~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-12-09 08:38:02 UTC
  • mfrom: (4878.1.1 integration2)
  • Revision ID: pqm@pqm.ubuntu.com-20091209083802-i7thqoq5irt2r4ya
(vila) Fix babune failures when LC_ALL=C for bug #73073 fix using
        lexists not exists

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(), """
31
30
    multiparent,
32
31
    osutils,
33
32
    revision as _mod_revision,
34
 
    ui,
35
33
    )
36
34
""")
37
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
 
                           ReusingTransform, CantMoveRoot,
 
36
                           ReusingTransform, NotVersionedError, CantMoveRoot,
39
37
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
40
38
                           UnableCreateSymlink)
41
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
50
48
    splitpath,
51
49
    supports_executable,
52
50
)
53
 
from bzrlib.progress import ProgressPhase
 
51
from bzrlib.progress import DummyProgress, ProgressPhase
54
52
from bzrlib.symbol_versioning import (
55
53
        deprecated_function,
56
54
        deprecated_in,
80
78
class TreeTransformBase(object):
81
79
    """The base class for TreeTransform and its kin."""
82
80
 
83
 
    def __init__(self, tree, pb=None,
 
81
    def __init__(self, tree, pb=DummyProgress(),
84
82
                 case_sensitive=True):
85
83
        """Constructor.
86
84
 
87
85
        :param tree: The tree that will be transformed, but not necessarily
88
86
            the output tree.
89
 
        :param pb: ignored
 
87
        :param pb: A ProgressTask indicating how much progress is being made
90
88
        :param case_sensitive: If True, the target of the transform is
91
89
            case sensitive, not just case preserving.
92
90
        """
163
161
 
164
162
    def adjust_path(self, name, parent, trans_id):
165
163
        """Change the path that is assigned to a transaction id."""
166
 
        if parent is None:
167
 
            raise ValueError("Parent trans-id may not be None")
168
164
        if trans_id == self._new_root:
169
165
            raise CantMoveRoot
170
166
        self._new_name[trans_id] = name
171
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
172
172
 
173
173
    def adjust_root_path(self, name, parent):
174
174
        """Emulate moving the root by moving all children, instead.
202
202
        self.version_file(old_root_file_id, old_root)
203
203
        self.unversion_file(self._new_root)
204
204
 
205
 
    def fixup_new_roots(self):
206
 
        """Reinterpret requests to change the root directory
207
 
 
208
 
        Instead of creating a root directory, or moving an existing directory,
209
 
        all the attributes and children of the new root are applied to the
210
 
        existing root directory.
211
 
 
212
 
        This means that the old root trans-id becomes obsolete, so it is
213
 
        recommended only to invoke this after the root trans-id has become
214
 
        irrelevant.
215
 
        """
216
 
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
217
 
                     ROOT_PARENT]
218
 
        if len(new_roots) < 1:
219
 
            return
220
 
        if len(new_roots) != 1:
221
 
            raise ValueError('A tree cannot have two roots!')
222
 
        if self._new_root is None:
223
 
            self._new_root = new_roots[0]
224
 
            return
225
 
        old_new_root = new_roots[0]
226
 
        # TODO: What to do if a old_new_root is present, but self._new_root is
227
 
        #       not listed as being removed? This code explicitly unversions
228
 
        #       the old root and versions it with the new file_id. Though that
229
 
        #       seems like an incomplete delta
230
 
 
231
 
        # unversion the new root's directory.
232
 
        file_id = self.final_file_id(old_new_root)
233
 
        if old_new_root in self._new_id:
234
 
            self.cancel_versioning(old_new_root)
235
 
        else:
236
 
            self.unversion_file(old_new_root)
237
 
        # if, at this stage, root still has an old file_id, zap it so we can
238
 
        # stick a new one in.
239
 
        if (self.tree_file_id(self._new_root) is not None and
240
 
            self._new_root not in self._removed_id):
241
 
            self.unversion_file(self._new_root)
242
 
        self.version_file(file_id, self._new_root)
243
 
 
244
 
        # Now move children of new root into old root directory.
245
 
        # Ensure all children are registered with the transaction, but don't
246
 
        # use directly-- some tree children have new parents
247
 
        list(self.iter_tree_children(old_new_root))
248
 
        # Move all children of new root into old root directory.
249
 
        for child in self.by_parent().get(old_new_root, []):
250
 
            self.adjust_path(self.final_name(child), self._new_root, child)
251
 
 
252
 
        # Ensure old_new_root has no directory.
253
 
        if old_new_root in self._new_contents:
254
 
            self.cancel_creation(old_new_root)
255
 
        else:
256
 
            self.delete_contents(old_new_root)
257
 
 
258
 
        # prevent deletion of root directory.
259
 
        if self._new_root in self._removed_contents:
260
 
            self.cancel_deletion(self._new_root)
261
 
 
262
 
        # destroy path info for old_new_root.
263
 
        del self._new_parent[old_new_root]
264
 
        del self._new_name[old_new_root]
265
 
 
266
205
    def trans_id_tree_file_id(self, inventory_id):
267
206
        """Determine the transaction id of a working tree file.
268
207
 
314
253
 
315
254
    def delete_contents(self, trans_id):
316
255
        """Schedule the contents of a path entry for deletion"""
317
 
        # Ensure that the object exists in the WorkingTree, this will raise an
318
 
        # exception if there is a problem
319
256
        self.tree_kind(trans_id)
320
257
        self._removed_contents.add(trans_id)
321
258
 
1063
1000
class DiskTreeTransform(TreeTransformBase):
1064
1001
    """Tree transform storing its contents on disk."""
1065
1002
 
1066
 
    def __init__(self, tree, limbodir, pb=None,
 
1003
    def __init__(self, tree, limbodir, pb=DummyProgress(),
1067
1004
                 case_sensitive=True):
1068
1005
        """Constructor.
1069
1006
        :param tree: The tree that will be transformed, but not necessarily
1070
1007
            the output tree.
1071
1008
        :param limbodir: A directory where new files can be stored until
1072
1009
            they are installed in their proper places
1073
 
        :param pb: ignored
 
1010
        :param pb: A ProgressBar indicating how much progress is being made
1074
1011
        :param case_sensitive: If True, the target of the transform is
1075
1012
            case sensitive, not just case preserving.
1076
1013
        """
1086
1023
        self._limbo_children_names = {}
1087
1024
        # List of transform ids that need to be renamed from limbo into place
1088
1025
        self._needs_rename = set()
1089
 
        self._creation_mtime = None
1090
1026
 
1091
1027
    def finalize(self):
1092
1028
        """Release the working tree lock, if held, clean up limbo dir.
1139
1075
        if (trans_id in self._limbo_files and
1140
1076
            trans_id not in self._needs_rename):
1141
1077
            self._rename_in_limbo([trans_id])
1142
 
            if previous_parent != parent:
1143
 
                self._limbo_children[previous_parent].remove(trans_id)
1144
 
            if previous_parent != parent or previous_name != name:
1145
 
                del self._limbo_children_names[previous_parent][previous_name]
 
1078
            self._limbo_children[previous_parent].remove(trans_id)
 
1079
            del self._limbo_children_names[previous_parent][previous_name]
1146
1080
 
1147
1081
    def _rename_in_limbo(self, trans_ids):
1148
1082
        """Fix limbo names so that the right final path is produced.
1160
1094
            if trans_id not in self._new_contents:
1161
1095
                continue
1162
1096
            new_path = self._limbo_name(trans_id)
1163
 
            osutils.rename(old_path, new_path)
 
1097
            os.rename(old_path, new_path)
1164
1098
            for descendant in self._limbo_descendants(trans_id):
1165
1099
                desc_path = self._limbo_files[descendant]
1166
1100
                desc_path = new_path + desc_path[len(old_path):]
1199
1133
            f.writelines(contents)
1200
1134
        finally:
1201
1135
            f.close()
1202
 
        self._set_mtime(name)
1203
1136
        self._set_mode(trans_id, mode_id, S_ISREG)
1204
1137
 
1205
1138
    def _read_file_chunks(self, trans_id):
1212
1145
    def _read_symlink_target(self, trans_id):
1213
1146
        return os.readlink(self._limbo_name(trans_id))
1214
1147
 
1215
 
    def _set_mtime(self, path):
1216
 
        """All files that are created get the same mtime.
1217
 
 
1218
 
        This time is set by the first object to be created.
1219
 
        """
1220
 
        if self._creation_mtime is None:
1221
 
            self._creation_mtime = time.time()
1222
 
        os.utime(path, (self._creation_mtime, self._creation_mtime))
1223
 
 
1224
1148
    def create_hardlink(self, path, trans_id):
1225
1149
        """Schedule creation of a hard link"""
1226
1150
        name = self._limbo_name(trans_id)
1340
1264
    FileMover does not delete files until it is sure that a rollback will not
1341
1265
    happen.
1342
1266
    """
1343
 
    def __init__(self, tree, pb=None):
 
1267
    def __init__(self, tree, pb=DummyProgress()):
1344
1268
        """Note: a tree_write lock is taken on the tree.
1345
1269
 
1346
1270
        Use TreeTransform.finalize() to release the lock (can be omitted if
1629
1553
                child_pb.update('removing file', num, len(tree_paths))
1630
1554
                full_path = self._tree.abspath(path)
1631
1555
                if trans_id in self._removed_contents:
1632
 
                    delete_path = os.path.join(self._deletiondir, trans_id)
1633
 
                    mover.pre_delete(full_path, delete_path)
1634
 
                elif (trans_id in self._new_name
1635
 
                      or trans_id in self._new_parent):
 
1556
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
 
1557
                                     trans_id))
 
1558
                elif trans_id in self._new_name or trans_id in \
 
1559
                    self._new_parent:
1636
1560
                    try:
1637
1561
                        mover.rename(full_path, self._limbo_name(trans_id))
1638
1562
                    except OSError, e:
1692
1616
    unversioned files in the input tree.
1693
1617
    """
1694
1618
 
1695
 
    def __init__(self, tree, pb=None, case_sensitive=True):
 
1619
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1696
1620
        tree.lock_read()
1697
1621
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1698
1622
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1798
1722
            executable = self.is_executable(file_id, path)
1799
1723
        return kind, executable, None
1800
1724
 
1801
 
    def is_locked(self):
1802
 
        return False
1803
 
 
1804
1725
    def lock_read(self):
1805
1726
        # Perhaps in theory, this should lock the TreeTransform?
1806
 
        return self
 
1727
        pass
1807
1728
 
1808
1729
    def unlock(self):
1809
1730
        pass
2593
2514
 
2594
2515
 
2595
2516
def revert(working_tree, target_tree, filenames, backups=False,
2596
 
           pb=None, change_reporter=None):
 
2517
           pb=DummyProgress(), change_reporter=None):
2597
2518
    """Revert a working tree's contents to those of a target tree."""
2598
2519
    target_tree.lock_read()
2599
 
    pb = ui.ui_factory.nested_progress_bar()
2600
2520
    tt = TreeTransform(working_tree, pb)
2601
2521
    try:
2602
2522
        pp = ProgressPhase("Revert phase", 3, pb)
2621
2541
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2622
2542
                              backups, pp, basis_tree=None,
2623
2543
                              merge_modified=None):
 
2544
    pp.next_phase()
2624
2545
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2625
2546
    try:
2626
2547
        if merge_modified is None:
2630
2551
                                      merge_modified, basis_tree)
2631
2552
    finally:
2632
2553
        child_pb.finished()
 
2554
    pp.next_phase()
2633
2555
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2634
2556
    try:
2635
2557
        raw_conflicts = resolve_conflicts(tt, child_pb,
2728
2650
                    parent_trans = ROOT_PARENT
2729
2651
                else:
2730
2652
                    parent_trans = tt.trans_id_file_id(parent[1])
2731
 
                if parent[0] is None and versioned[0]:
2732
 
                    tt.adjust_root_path(name[1], parent_trans)
2733
 
                else:
2734
 
                    tt.adjust_path(name[1], parent_trans, trans_id)
 
2653
                tt.adjust_path(name[1], parent_trans, trans_id)
2735
2654
            if executable[0] != executable[1] and kind[1] == "file":
2736
2655
                tt.set_executability(executable[1], trans_id)
2737
2656
        if working_tree.supports_content_filtering():
2750
2669
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2751
2670
                deferred_files):
2752
2671
                tt.create_file(bytes, trans_id, mode_id)
2753
 
        tt.fixup_new_roots()
2754
2672
    finally:
2755
2673
        if basis_tree is not None:
2756
2674
            basis_tree.unlock()
2757
2675
    return merge_modified
2758
2676
 
2759
2677
 
2760
 
def resolve_conflicts(tt, pb=None, pass_func=None):
 
2678
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2761
2679
    """Make many conflict-resolution attempts, but die if they fail"""
2762
2680
    if pass_func is None:
2763
2681
        pass_func = conflict_pass
2764
2682
    new_conflicts = set()
2765
 
    pb = ui.ui_factory.nested_progress_bar()
2766
2683
    try:
2767
2684
        for n in range(10):
2768
2685
            pb.update('Resolution pass', n+1, 10)
2772
2689
            new_conflicts.update(pass_func(tt, conflicts))
2773
2690
        raise MalformedTransform(conflicts=conflicts)
2774
2691
    finally:
2775
 
        pb.finished()
 
2692
        pb.clear()
2776
2693
 
2777
2694
 
2778
2695
def conflict_pass(tt, conflicts, path_tree=None):
2827
2744
                        # special-case the other tree root (move its
2828
2745
                        # children to current root)
2829
2746
                        if entry.parent_id is None:
2830
 
                            create = False
 
2747
                            create=False
2831
2748
                            moved = _reparent_transform_children(
2832
2749
                                tt, trans_id, tt.root)
2833
2750
                            for child in moved:
2901
2818
        self.pending_deletions = []
2902
2819
 
2903
2820
    def rename(self, from_, to):
2904
 
        """Rename a file from one path to another."""
 
2821
        """Rename a file from one path to another.  Functions like os.rename"""
2905
2822
        try:
2906
 
            osutils.rename(from_, to)
 
2823
            os.rename(from_, to)
2907
2824
        except OSError, e:
2908
2825
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2909
2826
                raise errors.FileExists(to, str(e))
2923
2840
    def rollback(self):
2924
2841
        """Reverse all renames that have been performed"""
2925
2842
        for from_, to in reversed(self.past_renames):
2926
 
            osutils.rename(to, from_)
 
2843
            os.rename(to, from_)
2927
2844
        # after rollback, don't reuse _FileMover
2928
2845
        past_renames = None
2929
2846
        pending_deletions = None