~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: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import os
20
18
import errno
21
19
from stat import S_ISREG, S_IEXEC
22
20
import time
23
21
 
24
22
from bzrlib import (
25
 
    config as _mod_config,
26
23
    errors,
27
24
    lazy_import,
28
25
    registry,
33
30
from bzrlib import (
34
31
    annotate,
35
32
    bencode,
36
 
    controldir,
 
33
    bzrdir,
37
34
    commit,
38
35
    conflicts,
39
36
    delta,
 
37
    errors,
40
38
    inventory,
41
39
    multiparent,
42
40
    osutils,
44
42
    ui,
45
43
    urlutils,
46
44
    )
47
 
from bzrlib.i18n import gettext
48
45
""")
49
 
from bzrlib.errors import (DuplicateKey, MalformedTransform,
 
46
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
50
47
                           ReusingTransform, CantMoveRoot,
51
 
                           ImmortalLimbo, NoFinalPath,
 
48
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
52
49
                           UnableCreateSymlink)
53
50
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
54
 
from bzrlib.mutabletree import MutableTree
55
51
from bzrlib.osutils import (
56
52
    delete_any,
57
53
    file_kind,
59
55
    pathjoin,
60
56
    sha_file,
61
57
    splitpath,
 
58
    supports_executable,
62
59
    )
63
60
from bzrlib.progress import ProgressPhase
64
61
from bzrlib.symbol_versioning import (
157
154
        """
158
155
        if self._tree is None:
159
156
            return
160
 
        for hook in MutableTree.hooks['post_transform']:
161
 
            hook(self._tree, self)
162
157
        self._tree.unlock()
163
158
        self._tree = None
164
159
 
233
228
        irrelevant.
234
229
 
235
230
        """
236
 
        new_roots = [k for k, v in self._new_parent.iteritems() if v ==
 
231
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
237
232
                     ROOT_PARENT]
238
233
        if len(new_roots) < 1:
239
234
            return
566
561
        for trans_id in self._removed_id:
567
562
            file_id = self.tree_file_id(trans_id)
568
563
            if file_id is not None:
569
 
                if self._tree.stored_kind(file_id) == 'directory':
 
564
                # XXX: This seems like something that should go via a different
 
565
                #      indirection.
 
566
                if self._tree.inventory[file_id].kind == 'directory':
570
567
                    parents.append(trans_id)
571
568
            elif self.tree_kind(trans_id) == 'directory':
572
569
                parents.append(trans_id)
575
572
            # ensure that all children are registered with the transaction
576
573
            list(self.iter_tree_children(parent_id))
577
574
 
 
575
    @deprecated_method(deprecated_in((2, 3, 0)))
 
576
    def has_named_child(self, by_parent, parent_id, name):
 
577
        return self._has_named_child(
 
578
            name, parent_id, known_children=by_parent.get(parent_id, []))
 
579
 
578
580
    def _has_named_child(self, name, parent_id, known_children):
579
581
        """Does a parent already have a name child.
580
582
 
624
626
        for trans_id in self._new_parent:
625
627
            seen = set()
626
628
            parent_id = trans_id
627
 
            while parent_id != ROOT_PARENT:
 
629
            while parent_id is not ROOT_PARENT:
628
630
                seen.add(parent_id)
629
631
                try:
630
632
                    parent_id = self.final_parent(parent_id)
640
642
        """If parent directories are versioned, children must be versioned."""
641
643
        conflicts = []
642
644
        for parent_id, children in by_parent.iteritems():
643
 
            if parent_id == ROOT_PARENT:
 
645
            if parent_id is ROOT_PARENT:
644
646
                continue
645
647
            if self.final_file_id(parent_id) is not None:
646
648
                continue
739
741
        """Children must have a directory parent"""
740
742
        conflicts = []
741
743
        for parent_id, children in by_parent.iteritems():
742
 
            if parent_id == ROOT_PARENT:
 
744
            if parent_id is ROOT_PARENT:
743
745
                continue
744
746
            no_children = True
745
747
            for child_id in children:
761
763
 
762
764
    def _set_executability(self, path, trans_id):
763
765
        """Set the executability of versioned files """
764
 
        if self._tree._supports_executable():
 
766
        if supports_executable():
765
767
            new_executability = self._new_executability[trans_id]
766
768
            abspath = self._tree.abspath(path)
767
769
            current_mode = os.stat(abspath).st_mode
776
778
                    to_mode |= 0010 & ~umask
777
779
            else:
778
780
                to_mode = current_mode & ~0111
779
 
            osutils.chmod_if_possible(abspath, to_mode)
 
781
            os.chmod(abspath, to_mode)
780
782
 
781
783
    def _new_entry(self, name, parent_id, file_id):
782
784
        """Helper function to create a new filesystem entry."""
1231
1233
        finally:
1232
1234
            TreeTransformBase.finalize(self)
1233
1235
 
1234
 
    def _limbo_supports_executable(self):
1235
 
        """Check if the limbo path supports the executable bit."""
1236
 
        # FIXME: Check actual file system capabilities of limbodir
1237
 
        return osutils.supports_executable()
1238
 
 
1239
1236
    def _limbo_name(self, trans_id):
1240
1237
        """Generate the limbo name of a file"""
1241
1238
        limbo_name = self._limbo_files.get(trans_id)
1401
1398
        delete_any(self._limbo_name(trans_id))
1402
1399
 
1403
1400
    def new_orphan(self, trans_id, parent_id):
1404
 
        conf = self._tree.get_config_stack()
1405
 
        handle_orphan = conf.get('bzr.transform.orphan_policy')
 
1401
        # FIXME: There is no tree config, so we use the branch one (it's weird
 
1402
        # to define it this way as orphaning can only occur in a working tree,
 
1403
        # but that's all we have (for now). It will find the option in
 
1404
        # locations.conf or bazaar.conf though) -- vila 20100916
 
1405
        conf = self._tree.branch.get_config()
 
1406
        conf_var_name = 'bzr.transform.orphan_policy'
 
1407
        orphan_policy = conf.get_user_option(conf_var_name)
 
1408
        default_policy = orphaning_registry.default_key
 
1409
        if orphan_policy is None:
 
1410
            orphan_policy = default_policy
 
1411
        if orphan_policy not in orphaning_registry:
 
1412
            trace.warning('%s (from %s) is not a known policy, defaulting '
 
1413
                'to %s' % (orphan_policy, conf_var_name, default_policy))
 
1414
            orphan_policy = default_policy
 
1415
        handle_orphan = orphaning_registry.get(orphan_policy)
1406
1416
        handle_orphan(self, trans_id, parent_id)
1407
1417
 
1408
1418
 
1471
1481
orphaning_registry._set_default_key('conflict')
1472
1482
 
1473
1483
 
1474
 
opt_transform_orphan = _mod_config.RegistryOption(
1475
 
    'bzr.transform.orphan_policy', orphaning_registry,
1476
 
    help='Policy for orphaned files during transform operations.',
1477
 
    invalid='warning')
1478
 
 
1479
 
 
1480
1484
class TreeTransform(DiskTreeTransform):
1481
1485
    """Represent a tree transformation.
1482
1486
 
1553
1557
        try:
1554
1558
            limbodir = urlutils.local_path_from_url(
1555
1559
                tree._transport.abspath('limbo'))
1556
 
            osutils.ensure_empty_directory_exists(
1557
 
                limbodir,
1558
 
                errors.ExistingLimbo)
 
1560
            try:
 
1561
                os.mkdir(limbodir)
 
1562
            except OSError, e:
 
1563
                if e.errno == errno.EEXIST:
 
1564
                    raise ExistingLimbo(limbodir)
1559
1565
            deletiondir = urlutils.local_path_from_url(
1560
1566
                tree._transport.abspath('pending-deletion'))
1561
 
            osutils.ensure_empty_directory_exists(
1562
 
                deletiondir,
1563
 
                errors.ExistingPendingDeletion)
 
1567
            try:
 
1568
                os.mkdir(deletiondir)
 
1569
            except OSError, e:
 
1570
                if e.errno == errno.EEXIST:
 
1571
                    raise errors.ExistingPendingDeletion(deletiondir)
1564
1572
        except:
1565
1573
            tree.unlock()
1566
1574
            raise
1629
1637
            else:
1630
1638
                raise
1631
1639
        if typefunc(mode):
1632
 
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
 
1640
            os.chmod(self._limbo_name(trans_id), mode)
1633
1641
 
1634
1642
    def iter_tree_children(self, parent_id):
1635
1643
        """Iterate through the entry's tree children, if any"""
1713
1721
            calculating one.
1714
1722
        :param _mover: Supply an alternate FileMover, for testing
1715
1723
        """
1716
 
        for hook in MutableTree.hooks['pre_transform']:
1717
 
            hook(self._tree, self)
1718
1724
        if not no_conflicts:
1719
1725
            self._check_malformed()
1720
1726
        child_pb = ui.ui_factory.nested_progress_bar()
1721
1727
        try:
1722
1728
            if precomputed_delta is None:
1723
 
                child_pb.update(gettext('Apply phase'), 0, 2)
 
1729
                child_pb.update('Apply phase', 0, 2)
1724
1730
                inventory_delta = self._generate_inventory_delta()
1725
1731
                offset = 1
1726
1732
            else:
1731
1737
            else:
1732
1738
                mover = _mover
1733
1739
            try:
1734
 
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
 
1740
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
1735
1741
                self._apply_removals(mover)
1736
 
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
 
1742
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
1737
1743
                modified_paths = self._apply_insertions(mover)
1738
1744
            except:
1739
1745
                mover.rollback()
1759
1765
        try:
1760
1766
            for num, trans_id in enumerate(self._removed_id):
1761
1767
                if (num % 10) == 0:
1762
 
                    child_pb.update(gettext('removing file'), num, total_entries)
 
1768
                    child_pb.update('removing file', num, total_entries)
1763
1769
                if trans_id == self._new_root:
1764
1770
                    file_id = self._tree.get_root_id()
1765
1771
                else:
1777
1783
            final_kinds = {}
1778
1784
            for num, (path, trans_id) in enumerate(new_paths):
1779
1785
                if (num % 10) == 0:
1780
 
                    child_pb.update(gettext('adding file'),
 
1786
                    child_pb.update('adding file',
1781
1787
                                    num + len(self._removed_id), total_entries)
1782
1788
                file_id = new_path_file_ids[trans_id]
1783
1789
                if file_id is None:
1827
1833
                # do not attempt to move root into a subdirectory of itself.
1828
1834
                if path == '':
1829
1835
                    continue
1830
 
                child_pb.update(gettext('removing file'), num, len(tree_paths))
 
1836
                child_pb.update('removing file', num, len(tree_paths))
1831
1837
                full_path = self._tree.abspath(path)
1832
1838
                if trans_id in self._removed_contents:
1833
1839
                    delete_path = os.path.join(self._deletiondir, trans_id)
1862
1868
        try:
1863
1869
            for num, (path, trans_id) in enumerate(new_paths):
1864
1870
                if (num % 10) == 0:
1865
 
                    child_pb.update(gettext('adding file'), num, len(new_paths))
 
1871
                    child_pb.update('adding file', num, len(new_paths))
1866
1872
                full_path = self._tree.abspath(path)
1867
1873
                if trans_id in self._needs_rename:
1868
1874
                    try:
2051
2057
        pass
2052
2058
 
2053
2059
    @property
2054
 
    @deprecated_method(deprecated_in((2, 5, 0)))
2055
2060
    def inventory(self):
2056
2061
        """This Tree does not use inventory as its backing data."""
2057
2062
        raise NotImplementedError(_PreviewTree.inventory)
2058
2063
 
2059
 
    @property
2060
 
    def root_inventory(self):
2061
 
        """This Tree does not use inventory as its backing data."""
2062
 
        raise NotImplementedError(_PreviewTree.root_inventory)
2063
 
 
2064
2064
    def get_root_id(self):
2065
2065
        return self._transform.final_file_id(self._transform.root)
2066
2066
 
2112
2112
        return cur_parent
2113
2113
 
2114
2114
    def path2id(self, path):
2115
 
        if isinstance(path, list):
2116
 
            if path == []:
2117
 
                path = [""]
2118
 
            path = osutils.pathjoin(*path)
2119
2115
        return self._transform.final_file_id(self._path2trans_id(path))
2120
2116
 
2121
2117
    def id2path(self, file_id):
2182
2178
                ordered_ids.append((trans_id, parent_file_id))
2183
2179
        return ordered_ids
2184
2180
 
2185
 
    def iter_child_entries(self, file_id, path=None):
2186
 
        self.id2path(file_id)
2187
 
        trans_id = self._transform.trans_id_file_id(file_id)
2188
 
        todo = [(child_trans_id, trans_id) for child_trans_id in
2189
 
                self._all_children(trans_id)]
2190
 
        for entry, trans_id in self._make_inv_entries(todo):
2191
 
            yield entry
2192
 
 
2193
2181
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2194
2182
        # This may not be a maximally efficient implementation, but it is
2195
2183
        # reasonably straightforward.  An implementation that grafts the
2337
2325
            if kind == 'file':
2338
2326
                statval = os.lstat(limbo_name)
2339
2327
                size = statval.st_size
2340
 
                if not tt._limbo_supports_executable():
 
2328
                if not supports_executable():
2341
2329
                    executable = False
2342
2330
                else:
2343
2331
                    executable = statval.st_mode & S_IEXEC
2558
2546
    file_trans_id = {}
2559
2547
    top_pb = ui.ui_factory.nested_progress_bar()
2560
2548
    pp = ProgressPhase("Build phase", 2, top_pb)
2561
 
    if tree.get_root_id() is not None:
 
2549
    if tree.inventory.root is not None:
2562
2550
        # This is kind of a hack: we should be altering the root
2563
2551
        # as part of the regular tree shape diff logic.
2564
2552
        # The conditional test here is to avoid doing an
2579
2567
        try:
2580
2568
            deferred_contents = []
2581
2569
            num = 0
2582
 
            total = len(tree.all_file_ids())
 
2570
            total = len(tree.inventory)
2583
2571
            if delta_from_tree:
2584
2572
                precomputed_delta = []
2585
2573
            else:
2594
2582
                for dir, files in wt.walkdirs():
2595
2583
                    existing_files.update(f[0] for f in files)
2596
2584
            for num, (tree_path, entry) in \
2597
 
                enumerate(tree.iter_entries_by_dir()):
2598
 
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
 
2585
                enumerate(tree.inventory.iter_entries_by_dir()):
 
2586
                pb.update("Building tree", num - len(deferred_contents), total)
2599
2587
                if entry.parent_id is None:
2600
2588
                    continue
2601
2589
                reparent = False
2607
2595
                    kind = file_kind(target_path)
2608
2596
                    if kind == "directory":
2609
2597
                        try:
2610
 
                            controldir.ControlDir.open(target_path)
 
2598
                            bzrdir.BzrDir.open(target_path)
2611
2599
                        except errors.NotBranchError:
2612
2600
                            pass
2613
2601
                        else:
2685
2673
                new_desired_files.append((file_id,
2686
2674
                    (trans_id, tree_path, text_sha1)))
2687
2675
                continue
2688
 
            pb.update(gettext('Adding file contents'), count + offset, total)
 
2676
            pb.update('Adding file contents', count + offset, total)
2689
2677
            if hardlink:
2690
2678
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2691
2679
                                   trans_id)
2712
2700
            contents = filtered_output_bytes(contents, filters,
2713
2701
                ContentFilterContext(tree_path, tree))
2714
2702
        tt.create_file(contents, trans_id, sha1=text_sha1)
2715
 
        pb.update(gettext('Adding file contents'), count + offset, total)
 
2703
        pb.update('Adding file contents', count + offset, total)
2716
2704
 
2717
2705
 
2718
2706
def _reparent_children(tt, old_parent, new_parent):
2832
2820
        tt.set_executability(entry.executable, trans_id)
2833
2821
 
2834
2822
 
 
2823
@deprecated_function(deprecated_in((2, 3, 0)))
 
2824
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
2825
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
2826
 
 
2827
 
 
2828
@deprecated_function(deprecated_in((2, 3, 0)))
 
2829
def _get_backup_name(name, by_parent, parent_trans_id, tt):
 
2830
    """Produce a backup-style name that appears to be available"""
 
2831
    def name_gen():
 
2832
        counter = 1
 
2833
        while True:
 
2834
            yield "%s.~%d~" % (name, counter)
 
2835
            counter += 1
 
2836
    for new_name in name_gen():
 
2837
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
2838
            return new_name
 
2839
 
 
2840
 
 
2841
def _entry_changes(file_id, entry, working_tree):
 
2842
    """Determine in which ways the inventory entry has changed.
 
2843
 
 
2844
    Returns booleans: has_contents, content_mod, meta_mod
 
2845
    has_contents means there are currently contents, but they differ
 
2846
    contents_mod means contents need to be modified
 
2847
    meta_mod means the metadata needs to be modified
 
2848
    """
 
2849
    cur_entry = working_tree.inventory[file_id]
 
2850
    try:
 
2851
        working_kind = working_tree.kind(file_id)
 
2852
        has_contents = True
 
2853
    except NoSuchFile:
 
2854
        has_contents = False
 
2855
        contents_mod = True
 
2856
        meta_mod = False
 
2857
    if has_contents is True:
 
2858
        if entry.kind != working_kind:
 
2859
            contents_mod, meta_mod = True, False
 
2860
        else:
 
2861
            cur_entry._read_tree_state(working_tree.id2path(file_id),
 
2862
                                       working_tree)
 
2863
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
 
2864
            cur_entry._forget_tree_state()
 
2865
    return has_contents, contents_mod, meta_mod
 
2866
 
 
2867
 
2835
2868
def revert(working_tree, target_tree, filenames, backups=False,
2836
2869
           pb=None, change_reporter=None):
2837
2870
    """Revert a working tree's contents to those of a target tree."""
3014
3047
    pb = ui.ui_factory.nested_progress_bar()
3015
3048
    try:
3016
3049
        for n in range(10):
3017
 
            pb.update(gettext('Resolution pass'), n+1, 10)
 
3050
            pb.update('Resolution pass', n+1, 10)
3018
3051
            conflicts = tt.find_conflicts()
3019
3052
            if len(conflicts) == 0:
3020
3053
                return new_conflicts
3044
3077
                existing_file, new_file = conflict[2], conflict[1]
3045
3078
            else:
3046
3079
                existing_file, new_file = conflict[1], conflict[2]
3047
 
            new_name = tt.final_name(existing_file) + '.moved'
 
3080
            new_name = tt.final_name(existing_file)+'.moved'
3048
3081
            tt.adjust_path(new_name, final_parent, existing_file)
3049
3082
            new_conflicts.add((c_type, 'Moved existing file to',
3050
3083
                               existing_file, new_file))