~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Martin Pool
  • Date: 2007-06-15 07:01:24 UTC
  • mfrom: (2528 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2530.
  • Revision ID: mbp@sourcefrog.net-20070615070124-clpwqh5gxc4wbf9l
Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
51
51
 
52
52
 
53
53
class _TransformResults(object):
54
 
    def __init__(self, modified_paths):
 
54
    def __init__(self, modified_paths, rename_count):
55
55
        object.__init__(self)
56
56
        self.modified_paths = modified_paths
 
57
        self.rename_count = rename_count
57
58
 
58
59
 
59
60
class TreeTransform(object):
60
61
    """Represent a tree transformation.
61
62
    
62
63
    This object is designed to support incremental generation of the transform,
63
 
    in any order.  
 
64
    in any order.
 
65
 
 
66
    However, it gives optimum performance when parent directories are created
 
67
    before their contents.  The transform is then able to put child files
 
68
    directly in their parent directory, avoiding later renames.
64
69
    
65
70
    It is easy to produce malformed transforms, but they are generally
66
71
    harmless.  Attempting to apply a malformed transform will cause an
84
89
    def __init__(self, tree, pb=DummyProgress()):
85
90
        """Note: a tree_write lock is taken on the tree.
86
91
        
87
 
        Use TreeTransform.finalize() to release the lock
 
92
        Use TreeTransform.finalize() to release the lock (can be omitted if
 
93
        TreeTransform.apply() called).
88
94
        """
89
95
        object.__init__(self)
90
96
        self._tree = tree
106
112
        self._new_name = {}
107
113
        self._new_parent = {}
108
114
        self._new_contents = {}
 
115
        # A mapping of transform ids to their limbo filename
 
116
        self._limbo_files = {}
 
117
        # A mapping of transform ids to a set of the transform ids of children
 
118
        # that their limbo directory has
 
119
        self._limbo_children = {}
 
120
        # Map transform ids to maps of child filename to child transform id
 
121
        self._limbo_children_names = {}
 
122
        # List of transform ids that need to be renamed from limbo into place
 
123
        self._needs_rename = set()
109
124
        self._removed_contents = set()
110
125
        self._new_executability = {}
111
126
        self._new_reference_revision = {}
115
130
        self._removed_id = set()
116
131
        self._tree_path_ids = {}
117
132
        self._tree_id_paths = {}
 
133
        # Cache of realpath results, to speed up canonical_path
118
134
        self._realpaths = {}
119
 
        # Cache of realpath results, to speed up canonical_path
 
135
        # Cache of relpath results, to speed up canonical_path
120
136
        self._relpaths = {}
121
 
        # Cache of relpath results, to speed up canonical_path
122
137
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
123
138
        self.__done = False
124
139
        self._pb = pb
 
140
        self.rename_count = 0
125
141
 
126
142
    def __get_root(self):
127
143
        return self._new_root
129
145
    root = property(__get_root)
130
146
 
131
147
    def finalize(self):
132
 
        """Release the working tree lock, if held, clean up limbo dir."""
 
148
        """Release the working tree lock, if held, clean up limbo dir.
 
149
 
 
150
        This is required if apply has not been invoked, but can be invoked
 
151
        even after apply.
 
152
        """
133
153
        if self._tree is None:
134
154
            return
135
155
        try:
136
 
            for trans_id, kind in self._new_contents.iteritems():
137
 
                path = self._limbo_name(trans_id)
 
156
            entries = [(self._limbo_name(t), t, k) for t, k in
 
157
                       self._new_contents.iteritems()]
 
158
            entries.sort(reverse=True)
 
159
            for path, trans_id, kind in entries:
138
160
                if kind == "directory":
139
161
                    os.rmdir(path)
140
162
                else:
165
187
        """Change the path that is assigned to a transaction id."""
166
188
        if trans_id == self._new_root:
167
189
            raise CantMoveRoot
 
190
        previous_parent = self._new_parent.get(trans_id)
 
191
        previous_name = self._new_name.get(trans_id)
168
192
        self._new_name[trans_id] = name
169
193
        self._new_parent[trans_id] = parent
 
194
        if (trans_id in self._limbo_files and
 
195
            trans_id not in self._needs_rename):
 
196
            self._rename_in_limbo([trans_id])
 
197
            self._limbo_children[previous_parent].remove(trans_id)
 
198
            del self._limbo_children_names[previous_parent][previous_name]
 
199
 
 
200
    def _rename_in_limbo(self, trans_ids):
 
201
        """Fix limbo names so that the right final path is produced.
 
202
 
 
203
        This means we outsmarted ourselves-- we tried to avoid renaming
 
204
        these files later by creating them with their final names in their
 
205
        final parents.  But now the previous name or parent is no longer
 
206
        suitable, so we have to rename them.
 
207
 
 
208
        Even for trans_ids that have no new contents, we must remove their
 
209
        entries from _limbo_files, because they are now stale.
 
210
        """
 
211
        for trans_id in trans_ids:
 
212
            old_path = self._limbo_files.pop(trans_id)
 
213
            if trans_id not in self._new_contents:
 
214
                continue
 
215
            new_path = self._limbo_name(trans_id)
 
216
            os.rename(old_path, new_path)
170
217
 
171
218
    def adjust_root_path(self, name, parent):
172
219
        """Emulate moving the root by moving all children, instead.
330
377
    def cancel_creation(self, trans_id):
331
378
        """Cancel the creation of new file contents."""
332
379
        del self._new_contents[trans_id]
 
380
        children = self._limbo_children.get(trans_id)
 
381
        # if this is a limbo directory with children, move them before removing
 
382
        # the directory
 
383
        if children is not None:
 
384
            self._rename_in_limbo(children)
 
385
            del self._limbo_children[trans_id]
 
386
            del self._limbo_children_names[trans_id]
333
387
        delete_any(self._limbo_name(trans_id))
334
388
 
335
389
    def delete_contents(self, trans_id):
734
788
        
735
789
        If filesystem or inventory conflicts are present, MalformedTransform
736
790
        will be thrown.
 
791
 
 
792
        If apply succeeds, finalize is not necessary.
737
793
        """
738
794
        conflicts = self.find_conflicts()
739
795
        if len(conflicts) != 0:
751
807
        self._tree.apply_inventory_delta(inventory_delta)
752
808
        self.__done = True
753
809
        self.finalize()
754
 
        return _TransformResults(modified_paths)
 
810
        return _TransformResults(modified_paths, self.rename_count)
755
811
 
756
812
    def _limbo_name(self, trans_id):
757
813
        """Generate the limbo name of a file"""
758
 
        return pathjoin(self._limbodir, trans_id)
 
814
        limbo_name = self._limbo_files.get(trans_id)
 
815
        if limbo_name is not None:
 
816
            return limbo_name
 
817
        parent = self._new_parent.get(trans_id)
 
818
        # if the parent directory is already in limbo (e.g. when building a
 
819
        # tree), choose a limbo name inside the parent, to reduce further
 
820
        # renames.
 
821
        use_direct_path = False
 
822
        if self._new_contents.get(parent) == 'directory':
 
823
            filename = self._new_name.get(trans_id)
 
824
            if filename is not None:
 
825
                if parent not in self._limbo_children:
 
826
                    self._limbo_children[parent] = set()
 
827
                    self._limbo_children_names[parent] = {}
 
828
                    use_direct_path = True
 
829
                # the direct path can only be used if no other file has
 
830
                # already taken this pathname, i.e. if the name is unused, or
 
831
                # if it is already associated with this trans_id.
 
832
                elif (self._limbo_children_names[parent].get(filename)
 
833
                      in (trans_id, None)):
 
834
                    use_direct_path = True
 
835
        if use_direct_path:
 
836
            limbo_name = pathjoin(self._limbo_files[parent], filename)
 
837
            self._limbo_children[parent].add(trans_id)
 
838
            self._limbo_children_names[parent][filename] = trans_id
 
839
        else:
 
840
            limbo_name = pathjoin(self._limbodir, trans_id)
 
841
            self._needs_rename.add(trans_id)
 
842
        self._limbo_files[trans_id] = limbo_name
 
843
        return limbo_name
759
844
 
760
845
    def _apply_removals(self, inv, inventory_delta):
761
846
        """Perform tree operations that remove directory/inventory names.
781
866
                    except OSError, e:
782
867
                        if e.errno != errno.ENOENT:
783
868
                            raise
 
869
                    else:
 
870
                        self.rename_count += 1
784
871
                if trans_id in self._removed_id:
785
872
                    if trans_id == self._new_root:
786
873
                        file_id = self._tree.inventory.root.file_id
812
899
                if trans_id in self._new_contents or \
813
900
                    self.path_changed(trans_id):
814
901
                    full_path = self._tree.abspath(path)
815
 
                    try:
816
 
                        os.rename(self._limbo_name(trans_id), full_path)
817
 
                    except OSError, e:
818
 
                        # We may be renaming a dangling inventory id
819
 
                        if e.errno != errno.ENOENT:
820
 
                            raise
 
902
                    if trans_id in self._needs_rename:
 
903
                        try:
 
904
                            os.rename(self._limbo_name(trans_id), full_path)
 
905
                        except OSError, e:
 
906
                            # We may be renaming a dangling inventory id
 
907
                            if e.errno != errno.ENOENT:
 
908
                                raise
 
909
                        else:
 
910
                            self.rename_count += 1
821
911
                    if trans_id in self._new_contents:
822
912
                        modified_paths.append(full_path)
823
913
                        del self._new_contents[trans_id]
1151
1241
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1152
1242
    pp = ProgressPhase("Build phase", 2, top_pb)
1153
1243
    if tree.inventory.root is not None:
1154
 
        # this is kindof a hack: we should be altering the root 
1155
 
        # as partof the regular tree shape diff logic.
1156
 
        # the conditional test hereis to avoid doing an
 
1244
        # This is kind of a hack: we should be altering the root
 
1245
        # as part of the regular tree shape diff logic.
 
1246
        # The conditional test here is to avoid doing an
1157
1247
        # expensive operation (flush) every time the root id
1158
1248
        # is set within the tree, nor setting the root and thus
1159
1249
        # marking the tree as dirty, because we use two different
1219
1309
            wt.add_conflicts(conflicts)
1220
1310
        except errors.UnsupportedOperation:
1221
1311
            pass
1222
 
        tt.apply()
 
1312
        result = tt.apply()
1223
1313
    finally:
1224
1314
        tt.finalize()
1225
1315
        top_pb.finished()
 
1316
    return result
1226
1317
 
1227
1318
 
1228
1319
def _reparent_children(tt, old_parent, new_parent):
1420
1511
        pp.next_phase()
1421
1512
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1422
1513
        try:
1423
 
            _alter_files(working_tree, target_tree, tt, child_pb,
1424
 
                         filenames, backups)
 
1514
            merge_modified = _alter_files(working_tree, target_tree, tt,
 
1515
                                          child_pb, filenames, backups)
1425
1516
        finally:
1426
1517
            child_pb.finished()
1427
1518
        pp.next_phase()
1439
1530
            warning(conflict)
1440
1531
        pp.next_phase()
1441
1532
        tt.apply()
1442
 
        working_tree.set_merge_modified({})
 
1533
        working_tree.set_merge_modified(merge_modified)
1443
1534
    finally:
1444
1535
        target_tree.unlock()
1445
1536
        tt.finalize()
1469
1560
                if kind[0] == 'file' and (backups or kind[1] is None):
1470
1561
                    wt_sha1 = working_tree.get_file_sha1(file_id)
1471
1562
                    if merge_modified.get(file_id) != wt_sha1:
1472
 
                        # acquire the basis tree lazyily to prevent the expense
1473
 
                        # of accessing it when its not needed ? (Guessing, RBC,
1474
 
                        # 200702)
 
1563
                        # acquire the basis tree lazily to prevent the
 
1564
                        # expense of accessing it when it's not needed ?
 
1565
                        # (Guessing, RBC, 200702)
1475
1566
                        if basis_tree is None:
1476
1567
                            basis_tree = working_tree.basis_tree()
1477
1568
                            basis_tree.lock_read()
1505
1596
                elif kind[1] == 'file':
1506
1597
                    tt.create_file(target_tree.get_file_lines(file_id),
1507
1598
                                   trans_id, mode_id)
 
1599
                    if basis_tree is None:
 
1600
                        basis_tree = working_tree.basis_tree()
 
1601
                        basis_tree.lock_read()
 
1602
                    new_sha1 = target_tree.get_file_sha1(file_id)
 
1603
                    if (file_id in basis_tree and new_sha1 ==
 
1604
                        basis_tree.get_file_sha1(file_id)):
 
1605
                        if file_id in merge_modified:
 
1606
                            del merge_modified[file_id]
 
1607
                    else:
 
1608
                        merge_modified[file_id] = new_sha1
 
1609
 
1508
1610
                    # preserve the execute bit when backing up
1509
1611
                    if keep_content and executable[0] == executable[1]:
1510
1612
                        tt.set_executability(executable[1], trans_id)
1523
1625
    finally:
1524
1626
        if basis_tree is not None:
1525
1627
            basis_tree.unlock()
 
1628
    return merge_modified
1526
1629
 
1527
1630
 
1528
1631
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):