~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2010-01-13 16:23:07 UTC
  • mto: (4634.119.7 2.0)
  • mto: This revision was merged to the branch mainline in revision 4959.
  • Revision ID: john@arbash-meinel.com-20100113162307-0bs82td16gzih827
Update the MANIFEST.in file.

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)
1164
 
            for descendant in self._limbo_descendants(trans_id):
1165
 
                desc_path = self._limbo_files[descendant]
1166
 
                desc_path = new_path + desc_path[len(old_path):]
1167
 
                self._limbo_files[descendant] = desc_path
1168
 
 
1169
 
    def _limbo_descendants(self, trans_id):
1170
 
        """Return the set of trans_ids whose limbo paths descend from this."""
1171
 
        descendants = set(self._limbo_children.get(trans_id, []))
1172
 
        for descendant in list(descendants):
1173
 
            descendants.update(self._limbo_descendants(descendant))
1174
 
        return descendants
 
1097
            os.rename(old_path, new_path)
1175
1098
 
1176
1099
    def create_file(self, contents, trans_id, mode_id=None):
1177
1100
        """Schedule creation of a new file.
1199
1122
            f.writelines(contents)
1200
1123
        finally:
1201
1124
            f.close()
1202
 
        self._set_mtime(name)
1203
1125
        self._set_mode(trans_id, mode_id, S_ISREG)
1204
1126
 
1205
1127
    def _read_file_chunks(self, trans_id):
1212
1134
    def _read_symlink_target(self, trans_id):
1213
1135
        return os.readlink(self._limbo_name(trans_id))
1214
1136
 
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
1137
    def create_hardlink(self, path, trans_id):
1225
1138
        """Schedule creation of a hard link"""
1226
1139
        name = self._limbo_name(trans_id)
1340
1253
    FileMover does not delete files until it is sure that a rollback will not
1341
1254
    happen.
1342
1255
    """
1343
 
    def __init__(self, tree, pb=None):
 
1256
    def __init__(self, tree, pb=DummyProgress()):
1344
1257
        """Note: a tree_write lock is taken on the tree.
1345
1258
 
1346
1259
        Use TreeTransform.finalize() to release the lock (can be omitted if
1629
1542
                child_pb.update('removing file', num, len(tree_paths))
1630
1543
                full_path = self._tree.abspath(path)
1631
1544
                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):
 
1545
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
 
1546
                                     trans_id))
 
1547
                elif trans_id in self._new_name or trans_id in \
 
1548
                    self._new_parent:
1636
1549
                    try:
1637
1550
                        mover.rename(full_path, self._limbo_name(trans_id))
1638
1551
                    except OSError, e:
1692
1605
    unversioned files in the input tree.
1693
1606
    """
1694
1607
 
1695
 
    def __init__(self, tree, pb=None, case_sensitive=True):
 
1608
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1696
1609
        tree.lock_read()
1697
1610
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1698
1611
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1989
1902
    def get_file_mtime(self, file_id, path=None):
1990
1903
        """See Tree.get_file_mtime"""
1991
1904
        if not self._content_change(file_id):
1992
 
            return self._transform._tree.get_file_mtime(file_id)
 
1905
            return self._transform._tree.get_file_mtime(file_id, path)
1993
1906
        return self._stat_limbo_file(file_id).st_mtime
1994
1907
 
1995
1908
    def _file_size(self, entry, stat_value):
2049
1962
                statval = os.lstat(limbo_name)
2050
1963
                size = statval.st_size
2051
1964
                if not supports_executable():
2052
 
                    executable = False
 
1965
                    executable = None
2053
1966
                else:
2054
1967
                    executable = statval.st_mode & S_IEXEC
2055
1968
            else:
2057
1970
                executable = None
2058
1971
            if kind == 'symlink':
2059
1972
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2060
 
        executable = tt._new_executability.get(trans_id, executable)
 
1973
        if supports_executable():
 
1974
            executable = tt._new_executability.get(trans_id, executable)
2061
1975
        return kind, size, executable, link_or_sha1
2062
1976
 
2063
1977
    def iter_changes(self, from_tree, include_unchanged=False,
2376
2290
        new_desired_files = desired_files
2377
2291
    else:
2378
2292
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2379
 
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2380
 
                     in iter if not (c or e[0] != e[1])]
2381
 
        if accelerator_tree.supports_content_filtering():
2382
 
            unchanged = [(f, p) for (f, p) in unchanged
2383
 
                         if not accelerator_tree.iter_search_rules([p]).next()]
2384
 
        unchanged = dict(unchanged)
 
2293
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
 
2294
                         in iter if not (c or e[0] != e[1]))
2385
2295
        new_desired_files = []
2386
2296
        count = 0
2387
2297
        for file_id, (trans_id, tree_path) in desired_files:
2590
2500
 
2591
2501
 
2592
2502
def revert(working_tree, target_tree, filenames, backups=False,
2593
 
           pb=None, change_reporter=None):
 
2503
           pb=DummyProgress(), change_reporter=None):
2594
2504
    """Revert a working tree's contents to those of a target tree."""
2595
2505
    target_tree.lock_read()
2596
 
    pb = ui.ui_factory.nested_progress_bar()
2597
2506
    tt = TreeTransform(working_tree, pb)
2598
2507
    try:
2599
2508
        pp = ProgressPhase("Revert phase", 3, pb)
2618
2527
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2619
2528
                              backups, pp, basis_tree=None,
2620
2529
                              merge_modified=None):
 
2530
    pp.next_phase()
2621
2531
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2622
2532
    try:
2623
2533
        if merge_modified is None:
2627
2537
                                      merge_modified, basis_tree)
2628
2538
    finally:
2629
2539
        child_pb.finished()
 
2540
    pp.next_phase()
2630
2541
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2631
2542
    try:
2632
2543
        raw_conflicts = resolve_conflicts(tt, child_pb,
2725
2636
                    parent_trans = ROOT_PARENT
2726
2637
                else:
2727
2638
                    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)
 
2639
                tt.adjust_path(name[1], parent_trans, trans_id)
2732
2640
            if executable[0] != executable[1] and kind[1] == "file":
2733
2641
                tt.set_executability(executable[1], trans_id)
2734
2642
        if working_tree.supports_content_filtering():
2747
2655
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2748
2656
                deferred_files):
2749
2657
                tt.create_file(bytes, trans_id, mode_id)
2750
 
        tt.fixup_new_roots()
2751
2658
    finally:
2752
2659
        if basis_tree is not None:
2753
2660
            basis_tree.unlock()
2754
2661
    return merge_modified
2755
2662
 
2756
2663
 
2757
 
def resolve_conflicts(tt, pb=None, pass_func=None):
 
2664
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2758
2665
    """Make many conflict-resolution attempts, but die if they fail"""
2759
2666
    if pass_func is None:
2760
2667
        pass_func = conflict_pass
2761
2668
    new_conflicts = set()
2762
 
    pb = ui.ui_factory.nested_progress_bar()
2763
2669
    try:
2764
2670
        for n in range(10):
2765
2671
            pb.update('Resolution pass', n+1, 10)
2769
2675
            new_conflicts.update(pass_func(tt, conflicts))
2770
2676
        raise MalformedTransform(conflicts=conflicts)
2771
2677
    finally:
2772
 
        pb.finished()
 
2678
        pb.clear()
2773
2679
 
2774
2680
 
2775
2681
def conflict_pass(tt, conflicts, path_tree=None):
2824
2730
                        # special-case the other tree root (move its
2825
2731
                        # children to current root)
2826
2732
                        if entry.parent_id is None:
2827
 
                            create = False
 
2733
                            create=False
2828
2734
                            moved = _reparent_transform_children(
2829
2735
                                tt, trans_id, tt.root)
2830
2736
                            for child in moved:
2898
2804
        self.pending_deletions = []
2899
2805
 
2900
2806
    def rename(self, from_, to):
2901
 
        """Rename a file from one path to another."""
 
2807
        """Rename a file from one path to another.  Functions like os.rename"""
2902
2808
        try:
2903
 
            osutils.rename(from_, to)
 
2809
            os.rename(from_, to)
2904
2810
        except OSError, e:
2905
2811
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2906
2812
                raise errors.FileExists(to, str(e))
2920
2826
    def rollback(self):
2921
2827
        """Reverse all renames that have been performed"""
2922
2828
        for from_, to in reversed(self.past_renames):
2923
 
            osutils.rename(to, from_)
 
2829
            os.rename(to, from_)
2924
2830
        # after rollback, don't reuse _FileMover
2925
2831
        past_renames = None
2926
2832
        pending_deletions = None