~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-09-19 00:32:14 UTC
  • mfrom: (4685.2.1 bzr.dev)
  • Revision ID: pqm@pqm.ubuntu.com-20090919003214-2dli9jc4y5xhjj3n
(mbp for garyvdm) Revert rename of
        test_merge_uncommitted_otherbasis_ancestor_of_thisbasis.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 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
17
17
import os
18
18
import errno
19
19
from stat import S_ISREG, S_IEXEC
20
 
import time
21
20
 
22
21
from bzrlib.lazy_import import lazy_import
23
22
lazy_import(globals(), """
162
161
 
163
162
    def adjust_path(self, name, parent, trans_id):
164
163
        """Change the path that is assigned to a transaction id."""
165
 
        if parent is None:
166
 
            raise ValueError("Parent trans-id may not be None")
167
164
        if trans_id == self._new_root:
168
165
            raise CantMoveRoot
169
166
        self._new_name[trans_id] = name
170
167
        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
171
172
 
172
173
    def adjust_root_path(self, name, parent):
173
174
        """Emulate moving the root by moving all children, instead.
201
202
        self.version_file(old_root_file_id, old_root)
202
203
        self.unversion_file(self._new_root)
203
204
 
204
 
    def fixup_new_roots(self):
205
 
        """Reinterpret requests to change the root directory
206
 
 
207
 
        Instead of creating a root directory, or moving an existing directory,
208
 
        all the attributes and children of the new root are applied to the
209
 
        existing root directory.
210
 
 
211
 
        This means that the old root trans-id becomes obsolete, so it is
212
 
        recommended only to invoke this after the root trans-id has become
213
 
        irrelevant.
214
 
        """
215
 
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
216
 
                     ROOT_PARENT]
217
 
        if len(new_roots) < 1:
218
 
            return
219
 
        if len(new_roots) != 1:
220
 
            raise ValueError('A tree cannot have two roots!')
221
 
        if self._new_root is None:
222
 
            self._new_root = new_roots[0]
223
 
            return
224
 
        old_new_root = new_roots[0]
225
 
        # TODO: What to do if a old_new_root is present, but self._new_root is
226
 
        #       not listed as being removed? This code explicitly unversions
227
 
        #       the old root and versions it with the new file_id. Though that
228
 
        #       seems like an incomplete delta
229
 
 
230
 
        # unversion the new root's directory.
231
 
        file_id = self.final_file_id(old_new_root)
232
 
        if old_new_root in self._new_id:
233
 
            self.cancel_versioning(old_new_root)
234
 
        else:
235
 
            self.unversion_file(old_new_root)
236
 
        # if, at this stage, root still has an old file_id, zap it so we can
237
 
        # stick a new one in.
238
 
        if (self.tree_file_id(self._new_root) is not None and
239
 
            self._new_root not in self._removed_id):
240
 
            self.unversion_file(self._new_root)
241
 
        self.version_file(file_id, self._new_root)
242
 
 
243
 
        # Now move children of new root into old root directory.
244
 
        # Ensure all children are registered with the transaction, but don't
245
 
        # use directly-- some tree children have new parents
246
 
        list(self.iter_tree_children(old_new_root))
247
 
        # Move all children of new root into old root directory.
248
 
        for child in self.by_parent().get(old_new_root, []):
249
 
            self.adjust_path(self.final_name(child), self._new_root, child)
250
 
 
251
 
        # Ensure old_new_root has no directory.
252
 
        if old_new_root in self._new_contents:
253
 
            self.cancel_creation(old_new_root)
254
 
        else:
255
 
            self.delete_contents(old_new_root)
256
 
 
257
 
        # prevent deletion of root directory.
258
 
        if self._new_root in self._removed_contents:
259
 
            self.cancel_deletion(self._new_root)
260
 
 
261
 
        # destroy path info for old_new_root.
262
 
        del self._new_parent[old_new_root]
263
 
        del self._new_name[old_new_root]
264
 
 
265
205
    def trans_id_tree_file_id(self, inventory_id):
266
206
        """Determine the transaction id of a working tree file.
267
207
 
313
253
 
314
254
    def delete_contents(self, trans_id):
315
255
        """Schedule the contents of a path entry for deletion"""
316
 
        # Ensure that the object exists in the WorkingTree, this will raise an
317
 
        # exception if there is a problem
318
256
        self.tree_kind(trans_id)
319
257
        self._removed_contents.add(trans_id)
320
258
 
921
859
    def get_preview_tree(self):
922
860
        """Return a tree representing the result of the transform.
923
861
 
924
 
        The tree is a snapshot, and altering the TreeTransform will invalidate
925
 
        it.
 
862
        This tree only supports the subset of Tree functionality required
 
863
        by show_diff_trees.  It must only be compared to tt._tree.
926
864
        """
927
865
        return _PreviewTree(self)
928
866
 
1085
1023
        self._limbo_children_names = {}
1086
1024
        # List of transform ids that need to be renamed from limbo into place
1087
1025
        self._needs_rename = set()
1088
 
        self._creation_mtime = None
1089
1026
 
1090
1027
    def finalize(self):
1091
1028
        """Release the working tree lock, if held, clean up limbo dir.
1117
1054
    def _limbo_name(self, trans_id):
1118
1055
        """Generate the limbo name of a file"""
1119
1056
        limbo_name = self._limbo_files.get(trans_id)
1120
 
        if limbo_name is None:
1121
 
            limbo_name = self._generate_limbo_path(trans_id)
1122
 
            self._limbo_files[trans_id] = limbo_name
 
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
1123
1096
        return limbo_name
1124
1097
 
1125
 
    def _generate_limbo_path(self, trans_id):
1126
 
        """Generate a limbo path using the trans_id as the relative path.
1127
 
 
1128
 
        This is suitable as a fallback, and when the transform should not be
1129
 
        sensitive to the path encoding of the limbo directory.
1130
 
        """
1131
 
        self._needs_rename.add(trans_id)
1132
 
        return pathjoin(self._limbodir, trans_id)
1133
 
 
1134
1098
    def adjust_path(self, name, parent, trans_id):
1135
1099
        previous_parent = self._new_parent.get(trans_id)
1136
1100
        previous_name = self._new_name.get(trans_id)
1138
1102
        if (trans_id in self._limbo_files and
1139
1103
            trans_id not in self._needs_rename):
1140
1104
            self._rename_in_limbo([trans_id])
1141
 
            if previous_parent != parent:
1142
 
                self._limbo_children[previous_parent].remove(trans_id)
1143
 
            if previous_parent != parent or previous_name != name:
1144
 
                del self._limbo_children_names[previous_parent][previous_name]
 
1105
            self._limbo_children[previous_parent].remove(trans_id)
 
1106
            del self._limbo_children_names[previous_parent][previous_name]
1145
1107
 
1146
1108
    def _rename_in_limbo(self, trans_ids):
1147
1109
        """Fix limbo names so that the right final path is produced.
1160
1122
                continue
1161
1123
            new_path = self._limbo_name(trans_id)
1162
1124
            os.rename(old_path, new_path)
1163
 
            for descendant in self._limbo_descendants(trans_id):
1164
 
                desc_path = self._limbo_files[descendant]
1165
 
                desc_path = new_path + desc_path[len(old_path):]
1166
 
                self._limbo_files[descendant] = desc_path
1167
 
 
1168
 
    def _limbo_descendants(self, trans_id):
1169
 
        """Return the set of trans_ids whose limbo paths descend from this."""
1170
 
        descendants = set(self._limbo_children.get(trans_id, []))
1171
 
        for descendant in list(descendants):
1172
 
            descendants.update(self._limbo_descendants(descendant))
1173
 
        return descendants
1174
1125
 
1175
1126
    def create_file(self, contents, trans_id, mode_id=None):
1176
1127
        """Schedule creation of a new file.
1198
1149
            f.writelines(contents)
1199
1150
        finally:
1200
1151
            f.close()
1201
 
        self._set_mtime(name)
1202
1152
        self._set_mode(trans_id, mode_id, S_ISREG)
1203
1153
 
1204
1154
    def _read_file_chunks(self, trans_id):
1211
1161
    def _read_symlink_target(self, trans_id):
1212
1162
        return os.readlink(self._limbo_name(trans_id))
1213
1163
 
1214
 
    def _set_mtime(self, path):
1215
 
        """All files that are created get the same mtime.
1216
 
 
1217
 
        This time is set by the first object to be created.
1218
 
        """
1219
 
        if self._creation_mtime is None:
1220
 
            self._creation_mtime = time.time()
1221
 
        os.utime(path, (self._creation_mtime, self._creation_mtime))
1222
 
 
1223
1164
    def create_hardlink(self, path, trans_id):
1224
1165
        """Schedule creation of a hard link"""
1225
1166
        name = self._limbo_name(trans_id)
1455
1396
                continue
1456
1397
            yield self.trans_id_tree_path(childpath)
1457
1398
 
1458
 
    def _generate_limbo_path(self, trans_id):
1459
 
        """Generate a limbo path using the final path if possible.
1460
 
 
1461
 
        This optimizes the performance of applying the tree transform by
1462
 
        avoiding renames.  These renames can be avoided only when the parent
1463
 
        directory is already scheduled for creation.
1464
 
 
1465
 
        If the final path cannot be used, falls back to using the trans_id as
1466
 
        the relpath.
1467
 
        """
1468
 
        parent = self._new_parent.get(trans_id)
1469
 
        # if the parent directory is already in limbo (e.g. when building a
1470
 
        # tree), choose a limbo name inside the parent, to reduce further
1471
 
        # renames.
1472
 
        use_direct_path = False
1473
 
        if self._new_contents.get(parent) == 'directory':
1474
 
            filename = self._new_name.get(trans_id)
1475
 
            if filename is not None:
1476
 
                if parent not in self._limbo_children:
1477
 
                    self._limbo_children[parent] = set()
1478
 
                    self._limbo_children_names[parent] = {}
1479
 
                    use_direct_path = True
1480
 
                # the direct path can only be used if no other file has
1481
 
                # already taken this pathname, i.e. if the name is unused, or
1482
 
                # if it is already associated with this trans_id.
1483
 
                elif self._case_sensitive_target:
1484
 
                    if (self._limbo_children_names[parent].get(filename)
1485
 
                        in (trans_id, None)):
1486
 
                        use_direct_path = True
1487
 
                else:
1488
 
                    for l_filename, l_trans_id in\
1489
 
                        self._limbo_children_names[parent].iteritems():
1490
 
                        if l_trans_id == trans_id:
1491
 
                            continue
1492
 
                        if l_filename.lower() == filename.lower():
1493
 
                            break
1494
 
                    else:
1495
 
                        use_direct_path = True
1496
 
 
1497
 
        if not use_direct_path:
1498
 
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
1499
 
 
1500
 
        limbo_name = pathjoin(self._limbo_files[parent], filename)
1501
 
        self._limbo_children[parent].add(trans_id)
1502
 
        self._limbo_children_names[parent][filename] = trans_id
1503
 
        return limbo_name
1504
 
 
1505
 
 
1506
1399
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1507
1400
        """Apply all changes to the inventory and filesystem.
1508
1401
 
1628
1521
                child_pb.update('removing file', num, len(tree_paths))
1629
1522
                full_path = self._tree.abspath(path)
1630
1523
                if trans_id in self._removed_contents:
1631
 
                    delete_path = os.path.join(self._deletiondir, trans_id)
1632
 
                    mover.pre_delete(full_path, delete_path)
1633
 
                elif (trans_id in self._new_name
1634
 
                      or trans_id in self._new_parent):
 
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:
1635
1528
                    try:
1636
1529
                        mover.rename(full_path, self._limbo_name(trans_id))
1637
1530
                    except OSError, e:
1742
1635
        self._all_children_cache = {}
1743
1636
        self._path2trans_id_cache = {}
1744
1637
        self._final_name_cache = {}
1745
 
        self._iter_changes_cache = dict((c[0], c) for c in
1746
 
                                        self._transform.iter_changes())
 
1638
 
 
1639
    def _changes(self, file_id):
 
1640
        for changes in self._transform.iter_changes():
 
1641
            if changes[0] == file_id:
 
1642
                return changes
1747
1643
 
1748
1644
    def _content_change(self, file_id):
1749
1645
        """Return True if the content of this file changed"""
1750
 
        changes = self._iter_changes_cache.get(file_id)
 
1646
        changes = self._changes(file_id)
1751
1647
        # changes[2] is true if the file content changed.  See
1752
1648
        # InterTree.iter_changes.
1753
1649
        return (changes is not None and changes[2])
1988
1884
    def get_file_mtime(self, file_id, path=None):
1989
1885
        """See Tree.get_file_mtime"""
1990
1886
        if not self._content_change(file_id):
1991
 
            return self._transform._tree.get_file_mtime(file_id)
 
1887
            return self._transform._tree.get_file_mtime(file_id, path)
1992
1888
        return self._stat_limbo_file(file_id).st_mtime
1993
1889
 
1994
1890
    def _file_size(self, entry, stat_value):
2048
1944
                statval = os.lstat(limbo_name)
2049
1945
                size = statval.st_size
2050
1946
                if not supports_executable():
2051
 
                    executable = False
 
1947
                    executable = None
2052
1948
                else:
2053
1949
                    executable = statval.st_mode & S_IEXEC
2054
1950
            else:
2056
1952
                executable = None
2057
1953
            if kind == 'symlink':
2058
1954
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2059
 
        executable = tt._new_executability.get(trans_id, executable)
 
1955
        if supports_executable():
 
1956
            executable = tt._new_executability.get(trans_id, executable)
2060
1957
        return kind, size, executable, link_or_sha1
2061
1958
 
2062
1959
    def iter_changes(self, from_tree, include_unchanged=False,
2093
1990
 
2094
1991
    def annotate_iter(self, file_id,
2095
1992
                      default_revision=_mod_revision.CURRENT_REVISION):
2096
 
        changes = self._iter_changes_cache.get(file_id)
 
1993
        changes = self._changes(file_id)
2097
1994
        if changes is None:
2098
1995
            get_old = True
2099
1996
        else:
2375
2272
        new_desired_files = desired_files
2376
2273
    else:
2377
2274
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2378
 
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2379
 
                     in iter if not (c or e[0] != e[1])]
2380
 
        if accelerator_tree.supports_content_filtering():
2381
 
            unchanged = [(f, p) for (f, p) in unchanged
2382
 
                         if not accelerator_tree.iter_search_rules([p]).next()]
2383
 
        unchanged = dict(unchanged)
 
2275
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
 
2276
                         in iter if not (c or e[0] != e[1]))
2384
2277
        new_desired_files = []
2385
2278
        count = 0
2386
2279
        for file_id, (trans_id, tree_path) in desired_files:
2509
2402
        tt.create_directory(trans_id)
2510
2403
 
2511
2404
 
2512
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2513
 
    filter_tree_path=None):
2514
 
    """Create new file contents according to tree contents.
2515
 
    
2516
 
    :param filter_tree_path: the tree path to use to lookup
2517
 
      content filters to apply to the bytes output in the working tree.
2518
 
      This only applies if the working tree supports content filtering.
2519
 
    """
 
2405
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
 
2406
    """Create new file contents according to tree contents."""
2520
2407
    kind = tree.kind(file_id)
2521
2408
    if kind == 'directory':
2522
2409
        tt.create_directory(trans_id)
2527
2414
                bytes = tree_file.readlines()
2528
2415
            finally:
2529
2416
                tree_file.close()
2530
 
        wt = tt._tree
2531
 
        if wt.supports_content_filtering() and filter_tree_path is not None:
2532
 
            filters = wt._content_filter_stack(filter_tree_path)
2533
 
            bytes = filtered_output_bytes(bytes, filters,
2534
 
                ContentFilterContext(filter_tree_path, tree))
2535
2417
        tt.create_file(bytes, trans_id)
2536
2418
    elif kind == "symlink":
2537
2419
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2725
2607
                    parent_trans = ROOT_PARENT
2726
2608
                else:
2727
2609
                    parent_trans = tt.trans_id_file_id(parent[1])
2728
 
                if parent[0] is None and versioned[0]:
2729
 
                    tt.adjust_root_path(name[1], parent_trans)
2730
 
                else:
2731
 
                    tt.adjust_path(name[1], parent_trans, trans_id)
 
2610
                tt.adjust_path(name[1], parent_trans, trans_id)
2732
2611
            if executable[0] != executable[1] and kind[1] == "file":
2733
2612
                tt.set_executability(executable[1], trans_id)
2734
 
        if working_tree.supports_content_filtering():
2735
 
            for index, ((trans_id, mode_id), bytes) in enumerate(
2736
 
                target_tree.iter_files_bytes(deferred_files)):
2737
 
                file_id = deferred_files[index][0]
2738
 
                # We're reverting a tree to the target tree so using the
2739
 
                # target tree to find the file path seems the best choice
2740
 
                # here IMO - Ian C 27/Oct/2009
2741
 
                filter_tree_path = target_tree.id2path(file_id)
2742
 
                filters = working_tree._content_filter_stack(filter_tree_path)
2743
 
                bytes = filtered_output_bytes(bytes, filters,
2744
 
                    ContentFilterContext(filter_tree_path, working_tree))
2745
 
                tt.create_file(bytes, trans_id, mode_id)
2746
 
        else:
2747
 
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2748
 
                deferred_files):
2749
 
                tt.create_file(bytes, trans_id, mode_id)
2750
 
        tt.fixup_new_roots()
 
2613
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2614
            deferred_files):
 
2615
            tt.create_file(bytes, trans_id, mode_id)
2751
2616
    finally:
2752
2617
        if basis_tree is not None:
2753
2618
            basis_tree.unlock()