/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 bzrlib/transform.py

  • Committer: Marius Kruger
  • Date: 2007-08-12 08:15:15 UTC
  • mfrom: (2695 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2979.
  • Revision ID: amanic@gmail.com-20070812081515-vgekipfhohcuj6rn
merge with bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
35
35
                            delete_any)
36
36
from bzrlib.progress import DummyProgress, ProgressPhase
37
 
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
 
37
from bzrlib.symbol_versioning import (
 
38
        deprecated_function,
 
39
        zero_fifteen,
 
40
        zero_ninety,
 
41
        )
38
42
from bzrlib.trace import mutter, warning
39
43
from bzrlib import tree
40
44
import bzrlib.ui
51
55
 
52
56
 
53
57
class _TransformResults(object):
54
 
    def __init__(self, modified_paths):
 
58
    def __init__(self, modified_paths, rename_count):
55
59
        object.__init__(self)
56
60
        self.modified_paths = modified_paths
 
61
        self.rename_count = rename_count
57
62
 
58
63
 
59
64
class TreeTransform(object):
60
65
    """Represent a tree transformation.
61
66
    
62
67
    This object is designed to support incremental generation of the transform,
63
 
    in any order.  
 
68
    in any order.
 
69
 
 
70
    However, it gives optimum performance when parent directories are created
 
71
    before their contents.  The transform is then able to put child files
 
72
    directly in their parent directory, avoiding later renames.
64
73
    
65
74
    It is easy to produce malformed transforms, but they are generally
66
75
    harmless.  Attempting to apply a malformed transform will cause an
84
93
    def __init__(self, tree, pb=DummyProgress()):
85
94
        """Note: a tree_write lock is taken on the tree.
86
95
        
87
 
        Use TreeTransform.finalize() to release the lock
 
96
        Use TreeTransform.finalize() to release the lock (can be omitted if
 
97
        TreeTransform.apply() called).
88
98
        """
89
99
        object.__init__(self)
90
100
        self._tree = tree
106
116
        self._new_name = {}
107
117
        self._new_parent = {}
108
118
        self._new_contents = {}
 
119
        # A mapping of transform ids to their limbo filename
 
120
        self._limbo_files = {}
 
121
        # A mapping of transform ids to a set of the transform ids of children
 
122
        # that their limbo directory has
 
123
        self._limbo_children = {}
 
124
        # Map transform ids to maps of child filename to child transform id
 
125
        self._limbo_children_names = {}
 
126
        # List of transform ids that need to be renamed from limbo into place
 
127
        self._needs_rename = set()
109
128
        self._removed_contents = set()
110
129
        self._new_executability = {}
111
130
        self._new_reference_revision = {}
115
134
        self._removed_id = set()
116
135
        self._tree_path_ids = {}
117
136
        self._tree_id_paths = {}
 
137
        # Cache of realpath results, to speed up canonical_path
118
138
        self._realpaths = {}
119
 
        # Cache of realpath results, to speed up canonical_path
 
139
        # Cache of relpath results, to speed up canonical_path
120
140
        self._relpaths = {}
121
 
        # Cache of relpath results, to speed up canonical_path
122
141
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
123
142
        self.__done = False
124
143
        self._pb = pb
 
144
        self.rename_count = 0
125
145
 
126
146
    def __get_root(self):
127
147
        return self._new_root
129
149
    root = property(__get_root)
130
150
 
131
151
    def finalize(self):
132
 
        """Release the working tree lock, if held, clean up limbo dir."""
 
152
        """Release the working tree lock, if held, clean up limbo dir.
 
153
 
 
154
        This is required if apply has not been invoked, but can be invoked
 
155
        even after apply.
 
156
        """
133
157
        if self._tree is None:
134
158
            return
135
159
        try:
136
 
            for trans_id, kind in self._new_contents.iteritems():
137
 
                path = self._limbo_name(trans_id)
 
160
            entries = [(self._limbo_name(t), t, k) for t, k in
 
161
                       self._new_contents.iteritems()]
 
162
            entries.sort(reverse=True)
 
163
            for path, trans_id, kind in entries:
138
164
                if kind == "directory":
139
165
                    os.rmdir(path)
140
166
                else:
165
191
        """Change the path that is assigned to a transaction id."""
166
192
        if trans_id == self._new_root:
167
193
            raise CantMoveRoot
 
194
        previous_parent = self._new_parent.get(trans_id)
 
195
        previous_name = self._new_name.get(trans_id)
168
196
        self._new_name[trans_id] = name
169
197
        self._new_parent[trans_id] = parent
 
198
        if (trans_id in self._limbo_files and
 
199
            trans_id not in self._needs_rename):
 
200
            self._rename_in_limbo([trans_id])
 
201
            self._limbo_children[previous_parent].remove(trans_id)
 
202
            del self._limbo_children_names[previous_parent][previous_name]
 
203
 
 
204
    def _rename_in_limbo(self, trans_ids):
 
205
        """Fix limbo names so that the right final path is produced.
 
206
 
 
207
        This means we outsmarted ourselves-- we tried to avoid renaming
 
208
        these files later by creating them with their final names in their
 
209
        final parents.  But now the previous name or parent is no longer
 
210
        suitable, so we have to rename them.
 
211
 
 
212
        Even for trans_ids that have no new contents, we must remove their
 
213
        entries from _limbo_files, because they are now stale.
 
214
        """
 
215
        for trans_id in trans_ids:
 
216
            old_path = self._limbo_files.pop(trans_id)
 
217
            if trans_id not in self._new_contents:
 
218
                continue
 
219
            new_path = self._limbo_name(trans_id)
 
220
            os.rename(old_path, new_path)
170
221
 
171
222
    def adjust_root_path(self, name, parent):
172
223
        """Emulate moving the root by moving all children, instead.
330
381
    def cancel_creation(self, trans_id):
331
382
        """Cancel the creation of new file contents."""
332
383
        del self._new_contents[trans_id]
 
384
        children = self._limbo_children.get(trans_id)
 
385
        # if this is a limbo directory with children, move them before removing
 
386
        # the directory
 
387
        if children is not None:
 
388
            self._rename_in_limbo(children)
 
389
            del self._limbo_children[trans_id]
 
390
            del self._limbo_children_names[trans_id]
333
391
        delete_any(self._limbo_name(trans_id))
334
392
 
335
393
    def delete_contents(self, trans_id):
663
721
    def _duplicate_entries(self, by_parent):
664
722
        """No directory may have two entries with the same name."""
665
723
        conflicts = []
 
724
        if (self._new_name, self._new_parent) == ({}, {}):
 
725
            return conflicts
666
726
        for children in by_parent.itervalues():
667
727
            name_ids = [(self.final_name(t), t) for t in children]
668
728
            name_ids.sort()
729
789
            return True
730
790
        return False
731
791
            
732
 
    def apply(self):
 
792
    def apply(self, no_conflicts=False):
733
793
        """Apply all changes to the inventory and filesystem.
734
794
        
735
795
        If filesystem or inventory conflicts are present, MalformedTransform
736
796
        will be thrown.
 
797
 
 
798
        If apply succeeds, finalize is not necessary.
 
799
 
 
800
        :param no_conflicts: if True, the caller guarantees there are no
 
801
            conflicts, so no check is made.
737
802
        """
738
 
        conflicts = self.find_conflicts()
739
 
        if len(conflicts) != 0:
740
 
            raise MalformedTransform(conflicts=conflicts)
 
803
        if not no_conflicts:
 
804
            conflicts = self.find_conflicts()
 
805
            if len(conflicts) != 0:
 
806
                raise MalformedTransform(conflicts=conflicts)
741
807
        inv = self._tree.inventory
742
808
        inventory_delta = []
743
809
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
751
817
        self._tree.apply_inventory_delta(inventory_delta)
752
818
        self.__done = True
753
819
        self.finalize()
754
 
        return _TransformResults(modified_paths)
 
820
        return _TransformResults(modified_paths, self.rename_count)
755
821
 
756
822
    def _limbo_name(self, trans_id):
757
823
        """Generate the limbo name of a file"""
758
 
        return pathjoin(self._limbodir, trans_id)
 
824
        limbo_name = self._limbo_files.get(trans_id)
 
825
        if limbo_name is not None:
 
826
            return limbo_name
 
827
        parent = self._new_parent.get(trans_id)
 
828
        # if the parent directory is already in limbo (e.g. when building a
 
829
        # tree), choose a limbo name inside the parent, to reduce further
 
830
        # renames.
 
831
        use_direct_path = False
 
832
        if self._new_contents.get(parent) == 'directory':
 
833
            filename = self._new_name.get(trans_id)
 
834
            if filename is not None:
 
835
                if parent not in self._limbo_children:
 
836
                    self._limbo_children[parent] = set()
 
837
                    self._limbo_children_names[parent] = {}
 
838
                    use_direct_path = True
 
839
                # the direct path can only be used if no other file has
 
840
                # already taken this pathname, i.e. if the name is unused, or
 
841
                # if it is already associated with this trans_id.
 
842
                elif (self._limbo_children_names[parent].get(filename)
 
843
                      in (trans_id, None)):
 
844
                    use_direct_path = True
 
845
        if use_direct_path:
 
846
            limbo_name = pathjoin(self._limbo_files[parent], filename)
 
847
            self._limbo_children[parent].add(trans_id)
 
848
            self._limbo_children_names[parent][filename] = trans_id
 
849
        else:
 
850
            limbo_name = pathjoin(self._limbodir, trans_id)
 
851
            self._needs_rename.add(trans_id)
 
852
        self._limbo_files[trans_id] = limbo_name
 
853
        return limbo_name
759
854
 
760
855
    def _apply_removals(self, inv, inventory_delta):
761
856
        """Perform tree operations that remove directory/inventory names.
781
876
                    except OSError, e:
782
877
                        if e.errno != errno.ENOENT:
783
878
                            raise
 
879
                    else:
 
880
                        self.rename_count += 1
784
881
                if trans_id in self._removed_id:
785
882
                    if trans_id == self._new_root:
786
883
                        file_id = self._tree.inventory.root.file_id
812
909
                if trans_id in self._new_contents or \
813
910
                    self.path_changed(trans_id):
814
911
                    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
 
912
                    if trans_id in self._needs_rename:
 
913
                        try:
 
914
                            os.rename(self._limbo_name(trans_id), full_path)
 
915
                        except OSError, e:
 
916
                            # We may be renaming a dangling inventory id
 
917
                            if e.errno != errno.ENOENT:
 
918
                                raise
 
919
                        else:
 
920
                            self.rename_count += 1
821
921
                    if trans_id in self._new_contents:
822
922
                        modified_paths.append(full_path)
823
923
                        del self._new_contents[trans_id]
1151
1251
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1152
1252
    pp = ProgressPhase("Build phase", 2, top_pb)
1153
1253
    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
 
1254
        # This is kind of a hack: we should be altering the root
 
1255
        # as part of the regular tree shape diff logic.
 
1256
        # The conditional test here is to avoid doing an
1157
1257
        # expensive operation (flush) every time the root id
1158
1258
        # is set within the tree, nor setting the root and thus
1159
1259
        # marking the tree as dirty, because we use two different
1219
1319
            wt.add_conflicts(conflicts)
1220
1320
        except errors.UnsupportedOperation:
1221
1321
            pass
1222
 
        tt.apply()
 
1322
        result = tt.apply()
1223
1323
    finally:
1224
1324
        tt.finalize()
1225
1325
        top_pb.finished()
 
1326
    return result
1226
1327
 
1227
1328
 
1228
1329
def _reparent_children(tt, old_parent, new_parent):
1327
1428
        working_tree.unlock()
1328
1429
 
1329
1430
 
 
1431
@deprecated_function(zero_ninety)
1330
1432
def change_entry(tt, file_id, working_tree, target_tree, 
1331
1433
                 trans_id_file_id, backups, trans_id, by_parent):
1332
1434
    """Replace a file_id's contents with those from a target tree."""
 
1435
    if file_id is None and target_tree is None:
 
1436
        # skip the logic altogether in the deprecation test
 
1437
        return
1333
1438
    e_trans_id = trans_id_file_id(file_id)
1334
1439
    entry = target_tree.inventory[file_id]
1335
1440
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
1420
1525
        pp.next_phase()
1421
1526
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1422
1527
        try:
1423
 
            _alter_files(working_tree, target_tree, tt, child_pb,
1424
 
                         filenames, backups)
 
1528
            merge_modified = _alter_files(working_tree, target_tree, tt,
 
1529
                                          child_pb, filenames, backups)
1425
1530
        finally:
1426
1531
            child_pb.finished()
1427
1532
        pp.next_phase()
1439
1544
            warning(conflict)
1440
1545
        pp.next_phase()
1441
1546
        tt.apply()
1442
 
        working_tree.set_merge_modified({})
 
1547
        working_tree.set_merge_modified(merge_modified)
1443
1548
    finally:
1444
1549
        target_tree.unlock()
1445
1550
        tt.finalize()
1469
1574
                if kind[0] == 'file' and (backups or kind[1] is None):
1470
1575
                    wt_sha1 = working_tree.get_file_sha1(file_id)
1471
1576
                    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)
 
1577
                        # acquire the basis tree lazily to prevent the
 
1578
                        # expense of accessing it when it's not needed ?
 
1579
                        # (Guessing, RBC, 200702)
1475
1580
                        if basis_tree is None:
1476
1581
                            basis_tree = working_tree.basis_tree()
1477
1582
                            basis_tree.lock_read()
1505
1610
                elif kind[1] == 'file':
1506
1611
                    tt.create_file(target_tree.get_file_lines(file_id),
1507
1612
                                   trans_id, mode_id)
 
1613
                    if basis_tree is None:
 
1614
                        basis_tree = working_tree.basis_tree()
 
1615
                        basis_tree.lock_read()
 
1616
                    new_sha1 = target_tree.get_file_sha1(file_id)
 
1617
                    if (file_id in basis_tree and new_sha1 ==
 
1618
                        basis_tree.get_file_sha1(file_id)):
 
1619
                        if file_id in merge_modified:
 
1620
                            del merge_modified[file_id]
 
1621
                    else:
 
1622
                        merge_modified[file_id] = new_sha1
 
1623
 
1508
1624
                    # preserve the execute bit when backing up
1509
1625
                    if keep_content and executable[0] == executable[1]:
1510
1626
                        tt.set_executability(executable[1], trans_id)
1523
1639
    finally:
1524
1640
        if basis_tree is not None:
1525
1641
            basis_tree.unlock()
 
1642
    return merge_modified
1526
1643
 
1527
1644
 
1528
1645
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1542
1659
        pb.clear()
1543
1660
 
1544
1661
 
1545
 
def conflict_pass(tt, conflicts):
1546
 
    """Resolve some classes of conflicts."""
 
1662
def conflict_pass(tt, conflicts, path_tree=None):
 
1663
    """Resolve some classes of conflicts.
 
1664
 
 
1665
    :param tt: The transform to resolve conflicts in
 
1666
    :param conflicts: The conflicts to resolve
 
1667
    :param path_tree: A Tree to get supplemental paths from
 
1668
    """
1547
1669
    new_conflicts = set()
1548
1670
    for c_type, conflict in ((c[0], c) for c in conflicts):
1549
1671
        if c_type == 'duplicate id':
1580
1702
            except KeyError:
1581
1703
                tt.create_directory(trans_id)
1582
1704
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
1705
                try:
 
1706
                    tt.final_name(trans_id)
 
1707
                except NoFinalPath:
 
1708
                    file_id = tt.final_file_id(trans_id)
 
1709
                    entry = path_tree.inventory[file_id]
 
1710
                    parent_trans_id = tt.trans_id_file_id(entry.parent_id)
 
1711
                    tt.adjust_path(entry.name, parent_trans_id, trans_id)
1583
1712
        elif c_type == 'unversioned parent':
1584
1713
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1585
1714
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))