~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2007-04-09 20:35:46 UTC
  • mto: This revision was merged to the branch mainline in revision 2566.
  • Revision ID: john@arbash-meinel.com-20070409203546-7935oty2xm5mj4j8
Add url to bazaar project page in docs

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, rename_count):
 
54
    def __init__(self, modified_paths):
55
55
        object.__init__(self)
56
56
        self.modified_paths = modified_paths
57
 
        self.rename_count = rename_count
58
57
 
59
58
 
60
59
class TreeTransform(object):
61
60
    """Represent a tree transformation.
62
61
    
63
62
    This object is designed to support incremental generation of the transform,
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.
 
63
    in any order.  
69
64
    
70
65
    It is easy to produce malformed transforms, but they are generally
71
66
    harmless.  Attempting to apply a malformed transform will cause an
89
84
    def __init__(self, tree, pb=DummyProgress()):
90
85
        """Note: a tree_write lock is taken on the tree.
91
86
        
92
 
        Use TreeTransform.finalize() to release the lock (can be omitted if
93
 
        TreeTransform.apply() called).
 
87
        Use TreeTransform.finalize() to release the lock
94
88
        """
95
89
        object.__init__(self)
96
90
        self._tree = tree
112
106
        self._new_name = {}
113
107
        self._new_parent = {}
114
108
        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()
124
109
        self._removed_contents = set()
125
110
        self._new_executability = {}
126
111
        self._new_reference_revision = {}
130
115
        self._removed_id = set()
131
116
        self._tree_path_ids = {}
132
117
        self._tree_id_paths = {}
 
118
        self._realpaths = {}
133
119
        # Cache of realpath results, to speed up canonical_path
134
 
        self._realpaths = {}
 
120
        self._relpaths = {}
135
121
        # Cache of relpath results, to speed up canonical_path
136
 
        self._relpaths = {}
137
122
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
138
123
        self.__done = False
139
124
        self._pb = pb
140
 
        self.rename_count = 0
141
125
 
142
126
    def __get_root(self):
143
127
        return self._new_root
145
129
    root = property(__get_root)
146
130
 
147
131
    def finalize(self):
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
 
        """
 
132
        """Release the working tree lock, if held, clean up limbo dir."""
153
133
        if self._tree is None:
154
134
            return
155
135
        try:
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:
 
136
            for trans_id, kind in self._new_contents.iteritems():
 
137
                path = self._limbo_name(trans_id)
160
138
                if kind == "directory":
161
139
                    os.rmdir(path)
162
140
                else:
187
165
        """Change the path that is assigned to a transaction id."""
188
166
        if trans_id == self._new_root:
189
167
            raise CantMoveRoot
190
 
        previous_parent = self._new_parent.get(trans_id)
191
 
        previous_name = self._new_name.get(trans_id)
192
168
        self._new_name[trans_id] = name
193
169
        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)
217
170
 
218
171
    def adjust_root_path(self, name, parent):
219
172
        """Emulate moving the root by moving all children, instead.
377
330
    def cancel_creation(self, trans_id):
378
331
        """Cancel the creation of new file contents."""
379
332
        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]
387
333
        delete_any(self._limbo_name(trans_id))
388
334
 
389
335
    def delete_contents(self, trans_id):
717
663
    def _duplicate_entries(self, by_parent):
718
664
        """No directory may have two entries with the same name."""
719
665
        conflicts = []
720
 
        if (self._new_name, self._new_parent) == ({}, {}):
721
 
            return conflicts
722
666
        for children in by_parent.itervalues():
723
667
            name_ids = [(self.final_name(t), t) for t in children]
724
668
            name_ids.sort()
785
729
            return True
786
730
        return False
787
731
            
788
 
    def apply(self, no_conflicts=False):
 
732
    def apply(self):
789
733
        """Apply all changes to the inventory and filesystem.
790
734
        
791
735
        If filesystem or inventory conflicts are present, MalformedTransform
792
736
        will be thrown.
793
 
 
794
 
        If apply succeeds, finalize is not necessary.
795
 
 
796
 
        :param no_conflicts: if True, the caller guarantees there are no
797
 
            conflicts, so no check is made.
798
737
        """
799
 
        if not no_conflicts:
800
 
            conflicts = self.find_conflicts()
801
 
            if len(conflicts) != 0:
802
 
                raise MalformedTransform(conflicts=conflicts)
 
738
        conflicts = self.find_conflicts()
 
739
        if len(conflicts) != 0:
 
740
            raise MalformedTransform(conflicts=conflicts)
803
741
        inv = self._tree.inventory
804
742
        inventory_delta = []
805
743
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
813
751
        self._tree.apply_inventory_delta(inventory_delta)
814
752
        self.__done = True
815
753
        self.finalize()
816
 
        return _TransformResults(modified_paths, self.rename_count)
 
754
        return _TransformResults(modified_paths)
817
755
 
818
756
    def _limbo_name(self, trans_id):
819
757
        """Generate the limbo name of a file"""
820
 
        limbo_name = self._limbo_files.get(trans_id)
821
 
        if limbo_name is not None:
822
 
            return limbo_name
823
 
        parent = self._new_parent.get(trans_id)
824
 
        # if the parent directory is already in limbo (e.g. when building a
825
 
        # tree), choose a limbo name inside the parent, to reduce further
826
 
        # renames.
827
 
        use_direct_path = False
828
 
        if self._new_contents.get(parent) == 'directory':
829
 
            filename = self._new_name.get(trans_id)
830
 
            if filename is not None:
831
 
                if parent not in self._limbo_children:
832
 
                    self._limbo_children[parent] = set()
833
 
                    self._limbo_children_names[parent] = {}
834
 
                    use_direct_path = True
835
 
                # the direct path can only be used if no other file has
836
 
                # already taken this pathname, i.e. if the name is unused, or
837
 
                # if it is already associated with this trans_id.
838
 
                elif (self._limbo_children_names[parent].get(filename)
839
 
                      in (trans_id, None)):
840
 
                    use_direct_path = True
841
 
        if use_direct_path:
842
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
843
 
            self._limbo_children[parent].add(trans_id)
844
 
            self._limbo_children_names[parent][filename] = trans_id
845
 
        else:
846
 
            limbo_name = pathjoin(self._limbodir, trans_id)
847
 
            self._needs_rename.add(trans_id)
848
 
        self._limbo_files[trans_id] = limbo_name
849
 
        return limbo_name
 
758
        return pathjoin(self._limbodir, trans_id)
850
759
 
851
760
    def _apply_removals(self, inv, inventory_delta):
852
761
        """Perform tree operations that remove directory/inventory names.
872
781
                    except OSError, e:
873
782
                        if e.errno != errno.ENOENT:
874
783
                            raise
875
 
                    else:
876
 
                        self.rename_count += 1
877
784
                if trans_id in self._removed_id:
878
785
                    if trans_id == self._new_root:
879
786
                        file_id = self._tree.inventory.root.file_id
905
812
                if trans_id in self._new_contents or \
906
813
                    self.path_changed(trans_id):
907
814
                    full_path = self._tree.abspath(path)
908
 
                    if trans_id in self._needs_rename:
909
 
                        try:
910
 
                            os.rename(self._limbo_name(trans_id), full_path)
911
 
                        except OSError, e:
912
 
                            # We may be renaming a dangling inventory id
913
 
                            if e.errno != errno.ENOENT:
914
 
                                raise
915
 
                        else:
916
 
                            self.rename_count += 1
 
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
917
821
                    if trans_id in self._new_contents:
918
822
                        modified_paths.append(full_path)
919
823
                        del self._new_contents[trans_id]
1247
1151
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1248
1152
    pp = ProgressPhase("Build phase", 2, top_pb)
1249
1153
    if tree.inventory.root is not None:
1250
 
        # This is kind of a hack: we should be altering the root
1251
 
        # as part of the regular tree shape diff logic.
1252
 
        # The conditional test here is to avoid doing an
 
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
1253
1157
        # expensive operation (flush) every time the root id
1254
1158
        # is set within the tree, nor setting the root and thus
1255
1159
        # marking the tree as dirty, because we use two different
1315
1219
            wt.add_conflicts(conflicts)
1316
1220
        except errors.UnsupportedOperation:
1317
1221
            pass
1318
 
        result = tt.apply()
 
1222
        tt.apply()
1319
1223
    finally:
1320
1224
        tt.finalize()
1321
1225
        top_pb.finished()
1322
 
    return result
1323
1226
 
1324
1227
 
1325
1228
def _reparent_children(tt, old_parent, new_parent):
1517
1420
        pp.next_phase()
1518
1421
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1519
1422
        try:
1520
 
            merge_modified = _alter_files(working_tree, target_tree, tt,
1521
 
                                          child_pb, filenames, backups)
 
1423
            _alter_files(working_tree, target_tree, tt, child_pb,
 
1424
                         filenames, backups)
1522
1425
        finally:
1523
1426
            child_pb.finished()
1524
1427
        pp.next_phase()
1536
1439
            warning(conflict)
1537
1440
        pp.next_phase()
1538
1441
        tt.apply()
1539
 
        working_tree.set_merge_modified(merge_modified)
 
1442
        working_tree.set_merge_modified({})
1540
1443
    finally:
1541
1444
        target_tree.unlock()
1542
1445
        tt.finalize()
1566
1469
                if kind[0] == 'file' and (backups or kind[1] is None):
1567
1470
                    wt_sha1 = working_tree.get_file_sha1(file_id)
1568
1471
                    if merge_modified.get(file_id) != wt_sha1:
1569
 
                        # acquire the basis tree lazily to prevent the
1570
 
                        # expense of accessing it when it's not needed ?
1571
 
                        # (Guessing, RBC, 200702)
 
1472
                        # acquire the basis tree lazyily to prevent the expense
 
1473
                        # of accessing it when its not needed ? (Guessing, RBC,
 
1474
                        # 200702)
1572
1475
                        if basis_tree is None:
1573
1476
                            basis_tree = working_tree.basis_tree()
1574
1477
                            basis_tree.lock_read()
1602
1505
                elif kind[1] == 'file':
1603
1506
                    tt.create_file(target_tree.get_file_lines(file_id),
1604
1507
                                   trans_id, mode_id)
1605
 
                    if basis_tree is None:
1606
 
                        basis_tree = working_tree.basis_tree()
1607
 
                        basis_tree.lock_read()
1608
 
                    new_sha1 = target_tree.get_file_sha1(file_id)
1609
 
                    if (file_id in basis_tree and new_sha1 ==
1610
 
                        basis_tree.get_file_sha1(file_id)):
1611
 
                        if file_id in merge_modified:
1612
 
                            del merge_modified[file_id]
1613
 
                    else:
1614
 
                        merge_modified[file_id] = new_sha1
1615
 
 
1616
1508
                    # preserve the execute bit when backing up
1617
1509
                    if keep_content and executable[0] == executable[1]:
1618
1510
                        tt.set_executability(executable[1], trans_id)
1631
1523
    finally:
1632
1524
        if basis_tree is not None:
1633
1525
            basis_tree.unlock()
1634
 
    return merge_modified
1635
1526
 
1636
1527
 
1637
1528
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1651
1542
        pb.clear()
1652
1543
 
1653
1544
 
1654
 
def conflict_pass(tt, conflicts, path_tree=None):
1655
 
    """Resolve some classes of conflicts.
1656
 
 
1657
 
    :param tt: The transform to resolve conflicts in
1658
 
    :param conflicts: The conflicts to resolve
1659
 
    :param path_tree: A Tree to get supplemental paths from
1660
 
    """
 
1545
def conflict_pass(tt, conflicts):
 
1546
    """Resolve some classes of conflicts."""
1661
1547
    new_conflicts = set()
1662
1548
    for c_type, conflict in ((c[0], c) for c in conflicts):
1663
1549
        if c_type == 'duplicate id':
1694
1580
            except KeyError:
1695
1581
                tt.create_directory(trans_id)
1696
1582
                new_conflicts.add((c_type, 'Created directory', trans_id))
1697
 
                try:
1698
 
                    tt.final_name(trans_id)
1699
 
                except NoFinalPath:
1700
 
                    file_id = tt.final_file_id(trans_id)
1701
 
                    entry = path_tree.inventory[file_id]
1702
 
                    parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1703
 
                    tt.adjust_path(entry.name, parent_trans_id, trans_id)
1704
1583
        elif c_type == 'unversioned parent':
1705
1584
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1706
1585
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))