~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

NEWS section template into a separate file

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
161
161
 
162
162
    def adjust_path(self, name, parent, trans_id):
163
163
        """Change the path that is assigned to a transaction id."""
 
164
        if parent is None:
 
165
            raise ValueError("Parent trans-id may not be None")
164
166
        if trans_id == self._new_root:
165
167
            raise CantMoveRoot
166
168
        self._new_name[trans_id] = name
167
169
        self._new_parent[trans_id] = parent
168
 
        if parent == ROOT_PARENT:
169
 
            if self._new_root is not None:
170
 
                raise ValueError("Cannot have multiple roots.")
171
 
            self._new_root = trans_id
172
170
 
173
171
    def adjust_root_path(self, name, parent):
174
172
        """Emulate moving the root by moving all children, instead.
202
200
        self.version_file(old_root_file_id, old_root)
203
201
        self.unversion_file(self._new_root)
204
202
 
 
203
    def fixup_new_roots(self):
 
204
        """Reinterpret requests to change the root directory
 
205
 
 
206
        Instead of creating a root directory, or moving an existing directory,
 
207
        all the attributes and children of the new root are applied to the
 
208
        existing root directory.
 
209
 
 
210
        This means that the old root trans-id becomes obsolete, so it is
 
211
        recommended only to invoke this after the root trans-id has become
 
212
        irrelevant.
 
213
        """
 
214
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
215
                     ROOT_PARENT]
 
216
        if len(new_roots) < 1:
 
217
            return
 
218
        if len(new_roots) != 1:
 
219
            raise ValueError('A tree cannot have two roots!')
 
220
        if self._new_root is None:
 
221
            self._new_root = new_roots[0]
 
222
            return
 
223
        old_new_root = new_roots[0]
 
224
        # TODO: What to do if a old_new_root is present, but self._new_root is
 
225
        #       not listed as being removed? This code explicitly unversions
 
226
        #       the old root and versions it with the new file_id. Though that
 
227
        #       seems like an incomplete delta
 
228
 
 
229
        # unversion the new root's directory.
 
230
        file_id = self.final_file_id(old_new_root)
 
231
        if old_new_root in self._new_id:
 
232
            self.cancel_versioning(old_new_root)
 
233
        else:
 
234
            self.unversion_file(old_new_root)
 
235
        # if, at this stage, root still has an old file_id, zap it so we can
 
236
        # stick a new one in.
 
237
        if (self.tree_file_id(self._new_root) is not None and
 
238
            self._new_root not in self._removed_id):
 
239
            self.unversion_file(self._new_root)
 
240
        self.version_file(file_id, self._new_root)
 
241
 
 
242
        # Now move children of new root into old root directory.
 
243
        # Ensure all children are registered with the transaction, but don't
 
244
        # use directly-- some tree children have new parents
 
245
        list(self.iter_tree_children(old_new_root))
 
246
        # Move all children of new root into old root directory.
 
247
        for child in self.by_parent().get(old_new_root, []):
 
248
            self.adjust_path(self.final_name(child), self._new_root, child)
 
249
 
 
250
        # Ensure old_new_root has no directory.
 
251
        if old_new_root in self._new_contents:
 
252
            self.cancel_creation(old_new_root)
 
253
        else:
 
254
            self.delete_contents(old_new_root)
 
255
 
 
256
        # prevent deletion of root directory.
 
257
        if self._new_root in self._removed_contents:
 
258
            self.cancel_deletion(self._new_root)
 
259
 
 
260
        # destroy path info for old_new_root.
 
261
        del self._new_parent[old_new_root]
 
262
        del self._new_name[old_new_root]
 
263
 
205
264
    def trans_id_tree_file_id(self, inventory_id):
206
265
        """Determine the transaction id of a working tree file.
207
266
 
253
312
 
254
313
    def delete_contents(self, trans_id):
255
314
        """Schedule the contents of a path entry for deletion"""
 
315
        # Ensure that the object exists in the WorkingTree, this will raise an
 
316
        # exception if there is a problem
256
317
        self.tree_kind(trans_id)
257
318
        self._removed_contents.add(trans_id)
258
319
 
859
920
    def get_preview_tree(self):
860
921
        """Return a tree representing the result of the transform.
861
922
 
862
 
        This tree only supports the subset of Tree functionality required
863
 
        by show_diff_trees.  It must only be compared to tt._tree.
 
923
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
924
        it.
864
925
        """
865
926
        return _PreviewTree(self)
866
927
 
1054
1115
    def _limbo_name(self, trans_id):
1055
1116
        """Generate the limbo name of a file"""
1056
1117
        limbo_name = self._limbo_files.get(trans_id)
1057
 
        if limbo_name is not None:
1058
 
            return limbo_name
1059
 
        parent = self._new_parent.get(trans_id)
1060
 
        # if the parent directory is already in limbo (e.g. when building a
1061
 
        # tree), choose a limbo name inside the parent, to reduce further
1062
 
        # renames.
1063
 
        use_direct_path = False
1064
 
        if self._new_contents.get(parent) == 'directory':
1065
 
            filename = self._new_name.get(trans_id)
1066
 
            if filename is not None:
1067
 
                if parent not in self._limbo_children:
1068
 
                    self._limbo_children[parent] = set()
1069
 
                    self._limbo_children_names[parent] = {}
1070
 
                    use_direct_path = True
1071
 
                # the direct path can only be used if no other file has
1072
 
                # already taken this pathname, i.e. if the name is unused, or
1073
 
                # if it is already associated with this trans_id.
1074
 
                elif self._case_sensitive_target:
1075
 
                    if (self._limbo_children_names[parent].get(filename)
1076
 
                        in (trans_id, None)):
1077
 
                        use_direct_path = True
1078
 
                else:
1079
 
                    for l_filename, l_trans_id in\
1080
 
                        self._limbo_children_names[parent].iteritems():
1081
 
                        if l_trans_id == trans_id:
1082
 
                            continue
1083
 
                        if l_filename.lower() == filename.lower():
1084
 
                            break
1085
 
                    else:
1086
 
                        use_direct_path = True
1087
 
 
1088
 
        if use_direct_path:
1089
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
1090
 
            self._limbo_children[parent].add(trans_id)
1091
 
            self._limbo_children_names[parent][filename] = trans_id
1092
 
        else:
1093
 
            limbo_name = pathjoin(self._limbodir, trans_id)
1094
 
            self._needs_rename.add(trans_id)
1095
 
        self._limbo_files[trans_id] = limbo_name
 
1118
        if limbo_name is None:
 
1119
            limbo_name = self._generate_limbo_path(trans_id)
 
1120
            self._limbo_files[trans_id] = limbo_name
1096
1121
        return limbo_name
1097
1122
 
 
1123
    def _generate_limbo_path(self, trans_id):
 
1124
        """Generate a limbo path using the trans_id as the relative path.
 
1125
 
 
1126
        This is suitable as a fallback, and when the transform should not be
 
1127
        sensitive to the path encoding of the limbo directory.
 
1128
        """
 
1129
        self._needs_rename.add(trans_id)
 
1130
        return pathjoin(self._limbodir, trans_id)
 
1131
 
1098
1132
    def adjust_path(self, name, parent, trans_id):
1099
1133
        previous_parent = self._new_parent.get(trans_id)
1100
1134
        previous_name = self._new_name.get(trans_id)
1102
1136
        if (trans_id in self._limbo_files and
1103
1137
            trans_id not in self._needs_rename):
1104
1138
            self._rename_in_limbo([trans_id])
1105
 
            self._limbo_children[previous_parent].remove(trans_id)
1106
 
            del self._limbo_children_names[previous_parent][previous_name]
 
1139
            if previous_parent != parent:
 
1140
                self._limbo_children[previous_parent].remove(trans_id)
 
1141
            if previous_parent != parent or previous_name != name:
 
1142
                del self._limbo_children_names[previous_parent][previous_name]
1107
1143
 
1108
1144
    def _rename_in_limbo(self, trans_ids):
1109
1145
        """Fix limbo names so that the right final path is produced.
1396
1432
                continue
1397
1433
            yield self.trans_id_tree_path(childpath)
1398
1434
 
 
1435
    def _generate_limbo_path(self, trans_id):
 
1436
        """Generate a limbo path using the final path if possible.
 
1437
 
 
1438
        This optimizes the performance of applying the tree transform by
 
1439
        avoiding renames.  These renames can be avoided only when the parent
 
1440
        directory is already scheduled for creation.
 
1441
 
 
1442
        If the final path cannot be used, falls back to using the trans_id as
 
1443
        the relpath.
 
1444
        """
 
1445
        parent = self._new_parent.get(trans_id)
 
1446
        # if the parent directory is already in limbo (e.g. when building a
 
1447
        # tree), choose a limbo name inside the parent, to reduce further
 
1448
        # renames.
 
1449
        use_direct_path = False
 
1450
        if self._new_contents.get(parent) == 'directory':
 
1451
            filename = self._new_name.get(trans_id)
 
1452
            if filename is not None:
 
1453
                if parent not in self._limbo_children:
 
1454
                    self._limbo_children[parent] = set()
 
1455
                    self._limbo_children_names[parent] = {}
 
1456
                    use_direct_path = True
 
1457
                # the direct path can only be used if no other file has
 
1458
                # already taken this pathname, i.e. if the name is unused, or
 
1459
                # if it is already associated with this trans_id.
 
1460
                elif self._case_sensitive_target:
 
1461
                    if (self._limbo_children_names[parent].get(filename)
 
1462
                        in (trans_id, None)):
 
1463
                        use_direct_path = True
 
1464
                else:
 
1465
                    for l_filename, l_trans_id in\
 
1466
                        self._limbo_children_names[parent].iteritems():
 
1467
                        if l_trans_id == trans_id:
 
1468
                            continue
 
1469
                        if l_filename.lower() == filename.lower():
 
1470
                            break
 
1471
                    else:
 
1472
                        use_direct_path = True
 
1473
 
 
1474
        if not use_direct_path:
 
1475
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1476
 
 
1477
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1478
        self._limbo_children[parent].add(trans_id)
 
1479
        self._limbo_children_names[parent][filename] = trans_id
 
1480
        return limbo_name
 
1481
 
 
1482
 
1399
1483
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1400
1484
        """Apply all changes to the inventory and filesystem.
1401
1485
 
1521
1605
                child_pb.update('removing file', num, len(tree_paths))
1522
1606
                full_path = self._tree.abspath(path)
1523
1607
                if trans_id in self._removed_contents:
1524
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
1525
 
                                     trans_id))
1526
 
                elif trans_id in self._new_name or trans_id in \
1527
 
                    self._new_parent:
 
1608
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
1609
                    mover.pre_delete(full_path, delete_path)
 
1610
                elif (trans_id in self._new_name
 
1611
                      or trans_id in self._new_parent):
1528
1612
                    try:
1529
1613
                        mover.rename(full_path, self._limbo_name(trans_id))
1530
1614
                    except OSError, e:
1635
1719
        self._all_children_cache = {}
1636
1720
        self._path2trans_id_cache = {}
1637
1721
        self._final_name_cache = {}
1638
 
 
1639
 
    def _changes(self, file_id):
1640
 
        for changes in self._transform.iter_changes():
1641
 
            if changes[0] == file_id:
1642
 
                return changes
 
1722
        self._iter_changes_cache = dict((c[0], c) for c in
 
1723
                                        self._transform.iter_changes())
1643
1724
 
1644
1725
    def _content_change(self, file_id):
1645
1726
        """Return True if the content of this file changed"""
1646
 
        changes = self._changes(file_id)
 
1727
        changes = self._iter_changes_cache.get(file_id)
1647
1728
        # changes[2] is true if the file content changed.  See
1648
1729
        # InterTree.iter_changes.
1649
1730
        return (changes is not None and changes[2])
1790
1871
            if self._transform.final_file_id(trans_id) is None:
1791
1872
                yield self._final_paths._determine_path(trans_id)
1792
1873
 
1793
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
 
1874
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
1875
        yield_parents=False):
1794
1876
        for trans_id, parent_file_id in ordered_entries:
1795
1877
            file_id = self._transform.final_file_id(trans_id)
1796
1878
            if file_id is None:
1822
1904
                ordered_ids.append((trans_id, parent_file_id))
1823
1905
        return ordered_ids
1824
1906
 
1825
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
1907
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1826
1908
        # This may not be a maximally efficient implementation, but it is
1827
1909
        # reasonably straightforward.  An implementation that grafts the
1828
1910
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1830
1912
        # position.
1831
1913
        ordered_ids = self._list_files_by_dir()
1832
1914
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1833
 
                                                      specific_file_ids):
 
1915
            specific_file_ids, yield_parents=yield_parents):
1834
1916
            yield unicode(self._final_paths.get_path(trans_id)), entry
1835
1917
 
1836
1918
    def _iter_entries_for_dir(self, dir_path):
1989
2071
 
1990
2072
    def annotate_iter(self, file_id,
1991
2073
                      default_revision=_mod_revision.CURRENT_REVISION):
1992
 
        changes = self._changes(file_id)
 
2074
        changes = self._iter_changes_cache.get(file_id)
1993
2075
        if changes is None:
1994
2076
            get_old = True
1995
2077
        else:
2401
2483
        tt.create_directory(trans_id)
2402
2484
 
2403
2485
 
2404
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2405
 
    """Create new file contents according to tree contents."""
 
2486
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2487
    filter_tree_path=None):
 
2488
    """Create new file contents according to tree contents.
 
2489
    
 
2490
    :param filter_tree_path: the tree path to use to lookup
 
2491
      content filters to apply to the bytes output in the working tree.
 
2492
      This only applies if the working tree supports content filtering.
 
2493
    """
2406
2494
    kind = tree.kind(file_id)
2407
2495
    if kind == 'directory':
2408
2496
        tt.create_directory(trans_id)
2413
2501
                bytes = tree_file.readlines()
2414
2502
            finally:
2415
2503
                tree_file.close()
 
2504
        wt = tt._tree
 
2505
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2506
            filters = wt._content_filter_stack(filter_tree_path)
 
2507
            bytes = filtered_output_bytes(bytes, filters,
 
2508
                ContentFilterContext(filter_tree_path, tree))
2416
2509
        tt.create_file(bytes, trans_id)
2417
2510
    elif kind == "symlink":
2418
2511
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2606
2699
                    parent_trans = ROOT_PARENT
2607
2700
                else:
2608
2701
                    parent_trans = tt.trans_id_file_id(parent[1])
2609
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
2702
                if parent[0] is None and versioned[0]:
 
2703
                    tt.adjust_root_path(name[1], parent_trans)
 
2704
                else:
 
2705
                    tt.adjust_path(name[1], parent_trans, trans_id)
2610
2706
            if executable[0] != executable[1] and kind[1] == "file":
2611
2707
                tt.set_executability(executable[1], trans_id)
2612
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2613
 
            deferred_files):
2614
 
            tt.create_file(bytes, trans_id, mode_id)
 
2708
        if working_tree.supports_content_filtering():
 
2709
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2710
                target_tree.iter_files_bytes(deferred_files)):
 
2711
                file_id = deferred_files[index][0]
 
2712
                # We're reverting a tree to the target tree so using the
 
2713
                # target tree to find the file path seems the best choice
 
2714
                # here IMO - Ian C 27/Oct/2009
 
2715
                filter_tree_path = target_tree.id2path(file_id)
 
2716
                filters = working_tree._content_filter_stack(filter_tree_path)
 
2717
                bytes = filtered_output_bytes(bytes, filters,
 
2718
                    ContentFilterContext(filter_tree_path, working_tree))
 
2719
                tt.create_file(bytes, trans_id, mode_id)
 
2720
        else:
 
2721
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2722
                deferred_files):
 
2723
                tt.create_file(bytes, trans_id, mode_id)
 
2724
        tt.fixup_new_roots()
2615
2725
    finally:
2616
2726
        if basis_tree is not None:
2617
2727
            basis_tree.unlock()