~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

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
 
17
19
import os
18
20
import errno
19
21
from stat import S_ISREG, S_IEXEC
20
22
import time
21
23
 
22
24
from bzrlib import (
 
25
    config as _mod_config,
23
26
    errors,
24
27
    lazy_import,
25
28
    registry,
30
33
from bzrlib import (
31
34
    annotate,
32
35
    bencode,
33
 
    bzrdir,
 
36
    controldir,
34
37
    commit,
35
38
    conflicts,
36
39
    delta,
37
 
    errors,
38
40
    inventory,
39
41
    multiparent,
40
42
    osutils,
44
46
    )
45
47
from bzrlib.i18n import gettext
46
48
""")
47
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
49
from bzrlib.errors import (DuplicateKey, MalformedTransform,
48
50
                           ReusingTransform, CantMoveRoot,
49
 
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
 
51
                           ImmortalLimbo, NoFinalPath,
50
52
                           UnableCreateSymlink)
51
53
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
 
54
from bzrlib.mutabletree import MutableTree
52
55
from bzrlib.osutils import (
53
56
    delete_any,
54
57
    file_kind,
56
59
    pathjoin,
57
60
    sha_file,
58
61
    splitpath,
59
 
    supports_executable,
60
62
    )
61
63
from bzrlib.progress import ProgressPhase
62
64
from bzrlib.symbol_versioning import (
155
157
        """
156
158
        if self._tree is None:
157
159
            return
 
160
        for hook in MutableTree.hooks['post_transform']:
 
161
            hook(self._tree, self)
158
162
        self._tree.unlock()
159
163
        self._tree = None
160
164
 
229
233
        irrelevant.
230
234
 
231
235
        """
232
 
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
236
        new_roots = [k for k, v in self._new_parent.iteritems() if v ==
233
237
                     ROOT_PARENT]
234
238
        if len(new_roots) < 1:
235
239
            return
562
566
        for trans_id in self._removed_id:
563
567
            file_id = self.tree_file_id(trans_id)
564
568
            if file_id is not None:
565
 
                # XXX: This seems like something that should go via a different
566
 
                #      indirection.
567
 
                if self._tree.inventory[file_id].kind == 'directory':
 
569
                if self._tree.stored_kind(file_id) == 'directory':
568
570
                    parents.append(trans_id)
569
571
            elif self.tree_kind(trans_id) == 'directory':
570
572
                parents.append(trans_id)
573
575
            # ensure that all children are registered with the transaction
574
576
            list(self.iter_tree_children(parent_id))
575
577
 
576
 
    @deprecated_method(deprecated_in((2, 3, 0)))
577
 
    def has_named_child(self, by_parent, parent_id, name):
578
 
        return self._has_named_child(
579
 
            name, parent_id, known_children=by_parent.get(parent_id, []))
580
 
 
581
578
    def _has_named_child(self, name, parent_id, known_children):
582
579
        """Does a parent already have a name child.
583
580
 
627
624
        for trans_id in self._new_parent:
628
625
            seen = set()
629
626
            parent_id = trans_id
630
 
            while parent_id is not ROOT_PARENT:
 
627
            while parent_id != ROOT_PARENT:
631
628
                seen.add(parent_id)
632
629
                try:
633
630
                    parent_id = self.final_parent(parent_id)
643
640
        """If parent directories are versioned, children must be versioned."""
644
641
        conflicts = []
645
642
        for parent_id, children in by_parent.iteritems():
646
 
            if parent_id is ROOT_PARENT:
 
643
            if parent_id == ROOT_PARENT:
647
644
                continue
648
645
            if self.final_file_id(parent_id) is not None:
649
646
                continue
742
739
        """Children must have a directory parent"""
743
740
        conflicts = []
744
741
        for parent_id, children in by_parent.iteritems():
745
 
            if parent_id is ROOT_PARENT:
 
742
            if parent_id == ROOT_PARENT:
746
743
                continue
747
744
            no_children = True
748
745
            for child_id in children:
764
761
 
765
762
    def _set_executability(self, path, trans_id):
766
763
        """Set the executability of versioned files """
767
 
        if supports_executable():
 
764
        if self._tree._supports_executable():
768
765
            new_executability = self._new_executability[trans_id]
769
766
            abspath = self._tree.abspath(path)
770
767
            current_mode = os.stat(abspath).st_mode
779
776
                    to_mode |= 0010 & ~umask
780
777
            else:
781
778
                to_mode = current_mode & ~0111
782
 
            os.chmod(abspath, to_mode)
 
779
            osutils.chmod_if_possible(abspath, to_mode)
783
780
 
784
781
    def _new_entry(self, name, parent_id, file_id):
785
782
        """Helper function to create a new filesystem entry."""
1234
1231
        finally:
1235
1232
            TreeTransformBase.finalize(self)
1236
1233
 
 
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
 
1237
1239
    def _limbo_name(self, trans_id):
1238
1240
        """Generate the limbo name of a file"""
1239
1241
        limbo_name = self._limbo_files.get(trans_id)
1399
1401
        delete_any(self._limbo_name(trans_id))
1400
1402
 
1401
1403
    def new_orphan(self, trans_id, parent_id):
1402
 
        # FIXME: There is no tree config, so we use the branch one (it's weird
1403
 
        # to define it this way as orphaning can only occur in a working tree,
1404
 
        # but that's all we have (for now). It will find the option in
1405
 
        # locations.conf or bazaar.conf though) -- vila 20100916
1406
 
        conf = self._tree.branch.get_config()
1407
 
        conf_var_name = 'bzr.transform.orphan_policy'
1408
 
        orphan_policy = conf.get_user_option(conf_var_name)
1409
 
        default_policy = orphaning_registry.default_key
1410
 
        if orphan_policy is None:
1411
 
            orphan_policy = default_policy
1412
 
        if orphan_policy not in orphaning_registry:
1413
 
            trace.warning('%s (from %s) is not a known policy, defaulting '
1414
 
                'to %s' % (orphan_policy, conf_var_name, default_policy))
1415
 
            orphan_policy = default_policy
1416
 
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1404
        conf = self._tree.get_config_stack()
 
1405
        handle_orphan = conf.get('bzr.transform.orphan_policy')
1417
1406
        handle_orphan(self, trans_id, parent_id)
1418
1407
 
1419
1408
 
1482
1471
orphaning_registry._set_default_key('conflict')
1483
1472
 
1484
1473
 
 
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
 
1485
1480
class TreeTransform(DiskTreeTransform):
1486
1481
    """Represent a tree transformation.
1487
1482
 
1558
1553
        try:
1559
1554
            limbodir = urlutils.local_path_from_url(
1560
1555
                tree._transport.abspath('limbo'))
1561
 
            try:
1562
 
                os.mkdir(limbodir)
1563
 
            except OSError, e:
1564
 
                if e.errno == errno.EEXIST:
1565
 
                    raise ExistingLimbo(limbodir)
 
1556
            osutils.ensure_empty_directory_exists(
 
1557
                limbodir,
 
1558
                errors.ExistingLimbo)
1566
1559
            deletiondir = urlutils.local_path_from_url(
1567
1560
                tree._transport.abspath('pending-deletion'))
1568
 
            try:
1569
 
                os.mkdir(deletiondir)
1570
 
            except OSError, e:
1571
 
                if e.errno == errno.EEXIST:
1572
 
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1561
            osutils.ensure_empty_directory_exists(
 
1562
                deletiondir,
 
1563
                errors.ExistingPendingDeletion)
1573
1564
        except:
1574
1565
            tree.unlock()
1575
1566
            raise
1638
1629
            else:
1639
1630
                raise
1640
1631
        if typefunc(mode):
1641
 
            os.chmod(self._limbo_name(trans_id), mode)
 
1632
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1642
1633
 
1643
1634
    def iter_tree_children(self, parent_id):
1644
1635
        """Iterate through the entry's tree children, if any"""
1722
1713
            calculating one.
1723
1714
        :param _mover: Supply an alternate FileMover, for testing
1724
1715
        """
 
1716
        for hook in MutableTree.hooks['pre_transform']:
 
1717
            hook(self._tree, self)
1725
1718
        if not no_conflicts:
1726
1719
            self._check_malformed()
1727
1720
        child_pb = ui.ui_factory.nested_progress_bar()
2058
2051
        pass
2059
2052
 
2060
2053
    @property
 
2054
    @deprecated_method(deprecated_in((2, 5, 0)))
2061
2055
    def inventory(self):
2062
2056
        """This Tree does not use inventory as its backing data."""
2063
2057
        raise NotImplementedError(_PreviewTree.inventory)
2064
2058
 
 
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
 
2065
2064
    def get_root_id(self):
2066
2065
        return self._transform.final_file_id(self._transform.root)
2067
2066
 
2113
2112
        return cur_parent
2114
2113
 
2115
2114
    def path2id(self, path):
 
2115
        if isinstance(path, list):
 
2116
            if path == []:
 
2117
                path = [""]
 
2118
            path = osutils.pathjoin(*path)
2116
2119
        return self._transform.final_file_id(self._path2trans_id(path))
2117
2120
 
2118
2121
    def id2path(self, file_id):
2179
2182
                ordered_ids.append((trans_id, parent_file_id))
2180
2183
        return ordered_ids
2181
2184
 
 
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
 
2182
2193
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2183
2194
        # This may not be a maximally efficient implementation, but it is
2184
2195
        # reasonably straightforward.  An implementation that grafts the
2326
2337
            if kind == 'file':
2327
2338
                statval = os.lstat(limbo_name)
2328
2339
                size = statval.st_size
2329
 
                if not supports_executable():
 
2340
                if not tt._limbo_supports_executable():
2330
2341
                    executable = False
2331
2342
                else:
2332
2343
                    executable = statval.st_mode & S_IEXEC
2547
2558
    file_trans_id = {}
2548
2559
    top_pb = ui.ui_factory.nested_progress_bar()
2549
2560
    pp = ProgressPhase("Build phase", 2, top_pb)
2550
 
    if tree.inventory.root is not None:
 
2561
    if tree.get_root_id() is not None:
2551
2562
        # This is kind of a hack: we should be altering the root
2552
2563
        # as part of the regular tree shape diff logic.
2553
2564
        # The conditional test here is to avoid doing an
2568
2579
        try:
2569
2580
            deferred_contents = []
2570
2581
            num = 0
2571
 
            total = len(tree.inventory)
 
2582
            total = len(tree.all_file_ids())
2572
2583
            if delta_from_tree:
2573
2584
                precomputed_delta = []
2574
2585
            else:
2583
2594
                for dir, files in wt.walkdirs():
2584
2595
                    existing_files.update(f[0] for f in files)
2585
2596
            for num, (tree_path, entry) in \
2586
 
                enumerate(tree.inventory.iter_entries_by_dir()):
 
2597
                enumerate(tree.iter_entries_by_dir()):
2587
2598
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2588
2599
                if entry.parent_id is None:
2589
2600
                    continue
2596
2607
                    kind = file_kind(target_path)
2597
2608
                    if kind == "directory":
2598
2609
                        try:
2599
 
                            bzrdir.BzrDir.open(target_path)
 
2610
                            controldir.ControlDir.open(target_path)
2600
2611
                        except errors.NotBranchError:
2601
2612
                            pass
2602
2613
                        else:
2821
2832
        tt.set_executability(entry.executable, trans_id)
2822
2833
 
2823
2834
 
2824
 
@deprecated_function(deprecated_in((2, 3, 0)))
2825
 
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2826
 
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2827
 
 
2828
 
 
2829
 
@deprecated_function(deprecated_in((2, 3, 0)))
2830
 
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2831
 
    """Produce a backup-style name that appears to be available"""
2832
 
    def name_gen():
2833
 
        counter = 1
2834
 
        while True:
2835
 
            yield "%s.~%d~" % (name, counter)
2836
 
            counter += 1
2837
 
    for new_name in name_gen():
2838
 
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2839
 
            return new_name
2840
 
 
2841
 
 
2842
 
def _entry_changes(file_id, entry, working_tree):
2843
 
    """Determine in which ways the inventory entry has changed.
2844
 
 
2845
 
    Returns booleans: has_contents, content_mod, meta_mod
2846
 
    has_contents means there are currently contents, but they differ
2847
 
    contents_mod means contents need to be modified
2848
 
    meta_mod means the metadata needs to be modified
2849
 
    """
2850
 
    cur_entry = working_tree.inventory[file_id]
2851
 
    try:
2852
 
        working_kind = working_tree.kind(file_id)
2853
 
        has_contents = True
2854
 
    except NoSuchFile:
2855
 
        has_contents = False
2856
 
        contents_mod = True
2857
 
        meta_mod = False
2858
 
    if has_contents is True:
2859
 
        if entry.kind != working_kind:
2860
 
            contents_mod, meta_mod = True, False
2861
 
        else:
2862
 
            cur_entry._read_tree_state(working_tree.id2path(file_id),
2863
 
                                       working_tree)
2864
 
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
2865
 
            cur_entry._forget_tree_state()
2866
 
    return has_contents, contents_mod, meta_mod
2867
 
 
2868
 
 
2869
2835
def revert(working_tree, target_tree, filenames, backups=False,
2870
2836
           pb=None, change_reporter=None):
2871
2837
    """Revert a working tree's contents to those of a target tree."""
3078
3044
                existing_file, new_file = conflict[2], conflict[1]
3079
3045
            else:
3080
3046
                existing_file, new_file = conflict[1], conflict[2]
3081
 
            new_name = tt.final_name(existing_file)+'.moved'
 
3047
            new_name = tt.final_name(existing_file) + '.moved'
3082
3048
            tt.adjust_path(new_name, final_parent, existing_file)
3083
3049
            new_conflicts.add((c_type, 'Moved existing file to',
3084
3050
                               existing_file, new_file))