~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,
42
44
    ui,
43
45
    urlutils,
44
46
    )
 
47
from bzrlib.i18n import gettext
45
48
""")
46
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
49
from bzrlib.errors import (DuplicateKey, MalformedTransform,
47
50
                           ReusingTransform, CantMoveRoot,
48
 
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
 
51
                           ImmortalLimbo, NoFinalPath,
49
52
                           UnableCreateSymlink)
50
53
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
 
54
from bzrlib.mutabletree import MutableTree
51
55
from bzrlib.osutils import (
52
56
    delete_any,
53
57
    file_kind,
55
59
    pathjoin,
56
60
    sha_file,
57
61
    splitpath,
58
 
    supports_executable,
59
62
    )
60
63
from bzrlib.progress import ProgressPhase
61
64
from bzrlib.symbol_versioning import (
154
157
        """
155
158
        if self._tree is None:
156
159
            return
 
160
        for hook in MutableTree.hooks['post_transform']:
 
161
            hook(self._tree, self)
157
162
        self._tree.unlock()
158
163
        self._tree = None
159
164
 
228
233
        irrelevant.
229
234
 
230
235
        """
231
 
        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 ==
232
237
                     ROOT_PARENT]
233
238
        if len(new_roots) < 1:
234
 
            if self.final_kind(self.root) is None:
235
 
                self.cancel_deletion(self.root)
236
 
            if self.final_file_id(self.root) is None:
237
 
                self.version_file(self.tree_file_id(self.root),
238
 
                                     self.root)
239
239
            return
240
240
        if len(new_roots) != 1:
241
241
            raise ValueError('A tree cannot have two roots!')
566
566
        for trans_id in self._removed_id:
567
567
            file_id = self.tree_file_id(trans_id)
568
568
            if file_id is not None:
569
 
                # XXX: This seems like something that should go via a different
570
 
                #      indirection.
571
 
                if self._tree.inventory[file_id].kind == 'directory':
 
569
                if self._tree.stored_kind(file_id) == 'directory':
572
570
                    parents.append(trans_id)
573
571
            elif self.tree_kind(trans_id) == 'directory':
574
572
                parents.append(trans_id)
577
575
            # ensure that all children are registered with the transaction
578
576
            list(self.iter_tree_children(parent_id))
579
577
 
580
 
    @deprecated_method(deprecated_in((2, 3, 0)))
581
 
    def has_named_child(self, by_parent, parent_id, name):
582
 
        return self._has_named_child(
583
 
            name, parent_id, known_children=by_parent.get(parent_id, []))
584
 
 
585
578
    def _has_named_child(self, name, parent_id, known_children):
586
579
        """Does a parent already have a name child.
587
580
 
631
624
        for trans_id in self._new_parent:
632
625
            seen = set()
633
626
            parent_id = trans_id
634
 
            while parent_id is not ROOT_PARENT:
 
627
            while parent_id != ROOT_PARENT:
635
628
                seen.add(parent_id)
636
629
                try:
637
630
                    parent_id = self.final_parent(parent_id)
647
640
        """If parent directories are versioned, children must be versioned."""
648
641
        conflicts = []
649
642
        for parent_id, children in by_parent.iteritems():
650
 
            if parent_id is ROOT_PARENT:
 
643
            if parent_id == ROOT_PARENT:
651
644
                continue
652
645
            if self.final_file_id(parent_id) is not None:
653
646
                continue
746
739
        """Children must have a directory parent"""
747
740
        conflicts = []
748
741
        for parent_id, children in by_parent.iteritems():
749
 
            if parent_id is ROOT_PARENT:
 
742
            if parent_id == ROOT_PARENT:
750
743
                continue
751
744
            no_children = True
752
745
            for child_id in children:
768
761
 
769
762
    def _set_executability(self, path, trans_id):
770
763
        """Set the executability of versioned files """
771
 
        if supports_executable():
 
764
        if self._tree._supports_executable():
772
765
            new_executability = self._new_executability[trans_id]
773
766
            abspath = self._tree.abspath(path)
774
767
            current_mode = os.stat(abspath).st_mode
783
776
                    to_mode |= 0010 & ~umask
784
777
            else:
785
778
                to_mode = current_mode & ~0111
786
 
            os.chmod(abspath, to_mode)
 
779
            osutils.chmod_if_possible(abspath, to_mode)
787
780
 
788
781
    def _new_entry(self, name, parent_id, file_id):
789
782
        """Helper function to create a new filesystem entry."""
1238
1231
        finally:
1239
1232
            TreeTransformBase.finalize(self)
1240
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
 
1241
1239
    def _limbo_name(self, trans_id):
1242
1240
        """Generate the limbo name of a file"""
1243
1241
        limbo_name = self._limbo_files.get(trans_id)
1403
1401
        delete_any(self._limbo_name(trans_id))
1404
1402
 
1405
1403
    def new_orphan(self, trans_id, parent_id):
1406
 
        # FIXME: There is no tree config, so we use the branch one (it's weird
1407
 
        # to define it this way as orphaning can only occur in a working tree,
1408
 
        # but that's all we have (for now). It will find the option in
1409
 
        # locations.conf or bazaar.conf though) -- vila 20100916
1410
 
        conf = self._tree.branch.get_config()
1411
 
        conf_var_name = 'bzr.transform.orphan_policy'
1412
 
        orphan_policy = conf.get_user_option(conf_var_name)
1413
 
        default_policy = orphaning_registry.default_key
1414
 
        if orphan_policy is None:
1415
 
            orphan_policy = default_policy
1416
 
        if orphan_policy not in orphaning_registry:
1417
 
            trace.warning('%s (from %s) is not a known policy, defaulting '
1418
 
                'to %s' % (orphan_policy, conf_var_name, default_policy))
1419
 
            orphan_policy = default_policy
1420
 
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1404
        conf = self._tree.get_config_stack()
 
1405
        handle_orphan = conf.get('bzr.transform.orphan_policy')
1421
1406
        handle_orphan(self, trans_id, parent_id)
1422
1407
 
1423
1408
 
1486
1471
orphaning_registry._set_default_key('conflict')
1487
1472
 
1488
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
 
1489
1480
class TreeTransform(DiskTreeTransform):
1490
1481
    """Represent a tree transformation.
1491
1482
 
1562
1553
        try:
1563
1554
            limbodir = urlutils.local_path_from_url(
1564
1555
                tree._transport.abspath('limbo'))
1565
 
            try:
1566
 
                os.mkdir(limbodir)
1567
 
            except OSError, e:
1568
 
                if e.errno == errno.EEXIST:
1569
 
                    raise ExistingLimbo(limbodir)
 
1556
            osutils.ensure_empty_directory_exists(
 
1557
                limbodir,
 
1558
                errors.ExistingLimbo)
1570
1559
            deletiondir = urlutils.local_path_from_url(
1571
1560
                tree._transport.abspath('pending-deletion'))
1572
 
            try:
1573
 
                os.mkdir(deletiondir)
1574
 
            except OSError, e:
1575
 
                if e.errno == errno.EEXIST:
1576
 
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1561
            osutils.ensure_empty_directory_exists(
 
1562
                deletiondir,
 
1563
                errors.ExistingPendingDeletion)
1577
1564
        except:
1578
1565
            tree.unlock()
1579
1566
            raise
1642
1629
            else:
1643
1630
                raise
1644
1631
        if typefunc(mode):
1645
 
            os.chmod(self._limbo_name(trans_id), mode)
 
1632
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1646
1633
 
1647
1634
    def iter_tree_children(self, parent_id):
1648
1635
        """Iterate through the entry's tree children, if any"""
1726
1713
            calculating one.
1727
1714
        :param _mover: Supply an alternate FileMover, for testing
1728
1715
        """
 
1716
        for hook in MutableTree.hooks['pre_transform']:
 
1717
            hook(self._tree, self)
1729
1718
        if not no_conflicts:
1730
1719
            self._check_malformed()
1731
1720
        child_pb = ui.ui_factory.nested_progress_bar()
1732
1721
        try:
1733
1722
            if precomputed_delta is None:
1734
 
                child_pb.update('Apply phase', 0, 2)
 
1723
                child_pb.update(gettext('Apply phase'), 0, 2)
1735
1724
                inventory_delta = self._generate_inventory_delta()
1736
1725
                offset = 1
1737
1726
            else:
1742
1731
            else:
1743
1732
                mover = _mover
1744
1733
            try:
1745
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1734
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1746
1735
                self._apply_removals(mover)
1747
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1736
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1748
1737
                modified_paths = self._apply_insertions(mover)
1749
1738
            except:
1750
1739
                mover.rollback()
1753
1742
                mover.apply_deletions()
1754
1743
        finally:
1755
1744
            child_pb.finished()
 
1745
        if self.final_file_id(self.root) is None:
 
1746
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1756
1747
        self._tree.apply_inventory_delta(inventory_delta)
1757
1748
        self._apply_observed_sha1s()
1758
1749
        self._done = True
1768
1759
        try:
1769
1760
            for num, trans_id in enumerate(self._removed_id):
1770
1761
                if (num % 10) == 0:
1771
 
                    child_pb.update('removing file', num, total_entries)
 
1762
                    child_pb.update(gettext('removing file'), num, total_entries)
1772
1763
                if trans_id == self._new_root:
1773
1764
                    file_id = self._tree.get_root_id()
1774
1765
                else:
1786
1777
            final_kinds = {}
1787
1778
            for num, (path, trans_id) in enumerate(new_paths):
1788
1779
                if (num % 10) == 0:
1789
 
                    child_pb.update('adding file',
 
1780
                    child_pb.update(gettext('adding file'),
1790
1781
                                    num + len(self._removed_id), total_entries)
1791
1782
                file_id = new_path_file_ids[trans_id]
1792
1783
                if file_id is None:
1836
1827
                # do not attempt to move root into a subdirectory of itself.
1837
1828
                if path == '':
1838
1829
                    continue
1839
 
                child_pb.update('removing file', num, len(tree_paths))
 
1830
                child_pb.update(gettext('removing file'), num, len(tree_paths))
1840
1831
                full_path = self._tree.abspath(path)
1841
1832
                if trans_id in self._removed_contents:
1842
1833
                    delete_path = os.path.join(self._deletiondir, trans_id)
1871
1862
        try:
1872
1863
            for num, (path, trans_id) in enumerate(new_paths):
1873
1864
                if (num % 10) == 0:
1874
 
                    child_pb.update('adding file', num, len(new_paths))
 
1865
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1875
1866
                full_path = self._tree.abspath(path)
1876
1867
                if trans_id in self._needs_rename:
1877
1868
                    try:
2060
2051
        pass
2061
2052
 
2062
2053
    @property
 
2054
    @deprecated_method(deprecated_in((2, 5, 0)))
2063
2055
    def inventory(self):
2064
2056
        """This Tree does not use inventory as its backing data."""
2065
2057
        raise NotImplementedError(_PreviewTree.inventory)
2066
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
 
2067
2064
    def get_root_id(self):
2068
2065
        return self._transform.final_file_id(self._transform.root)
2069
2066
 
2115
2112
        return cur_parent
2116
2113
 
2117
2114
    def path2id(self, path):
 
2115
        if isinstance(path, list):
 
2116
            if path == []:
 
2117
                path = [""]
 
2118
            path = osutils.pathjoin(*path)
2118
2119
        return self._transform.final_file_id(self._path2trans_id(path))
2119
2120
 
2120
2121
    def id2path(self, file_id):
2181
2182
                ordered_ids.append((trans_id, parent_file_id))
2182
2183
        return ordered_ids
2183
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
 
2184
2193
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2185
2194
        # This may not be a maximally efficient implementation, but it is
2186
2195
        # reasonably straightforward.  An implementation that grafts the
2261
2270
        else:
2262
2271
            return None
2263
2272
 
 
2273
    def get_file_verifier(self, file_id, path=None, stat_value=None):
 
2274
        trans_id = self._transform.trans_id_file_id(file_id)
 
2275
        kind = self._transform._new_contents.get(trans_id)
 
2276
        if kind is None:
 
2277
            return self._transform._tree.get_file_verifier(file_id)
 
2278
        if kind == 'file':
 
2279
            fileobj = self.get_file(file_id)
 
2280
            try:
 
2281
                return ("SHA1", sha_file(fileobj))
 
2282
            finally:
 
2283
                fileobj.close()
 
2284
 
2264
2285
    def get_file_sha1(self, file_id, path=None, stat_value=None):
2265
2286
        trans_id = self._transform.trans_id_file_id(file_id)
2266
2287
        kind = self._transform._new_contents.get(trans_id)
2316
2337
            if kind == 'file':
2317
2338
                statval = os.lstat(limbo_name)
2318
2339
                size = statval.st_size
2319
 
                if not supports_executable():
 
2340
                if not tt._limbo_supports_executable():
2320
2341
                    executable = False
2321
2342
                else:
2322
2343
                    executable = statval.st_mode & S_IEXEC
2537
2558
    file_trans_id = {}
2538
2559
    top_pb = ui.ui_factory.nested_progress_bar()
2539
2560
    pp = ProgressPhase("Build phase", 2, top_pb)
2540
 
    if tree.inventory.root is not None:
 
2561
    if tree.get_root_id() is not None:
2541
2562
        # This is kind of a hack: we should be altering the root
2542
2563
        # as part of the regular tree shape diff logic.
2543
2564
        # The conditional test here is to avoid doing an
2558
2579
        try:
2559
2580
            deferred_contents = []
2560
2581
            num = 0
2561
 
            total = len(tree.inventory)
 
2582
            total = len(tree.all_file_ids())
2562
2583
            if delta_from_tree:
2563
2584
                precomputed_delta = []
2564
2585
            else:
2573
2594
                for dir, files in wt.walkdirs():
2574
2595
                    existing_files.update(f[0] for f in files)
2575
2596
            for num, (tree_path, entry) in \
2576
 
                enumerate(tree.inventory.iter_entries_by_dir()):
2577
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
2597
                enumerate(tree.iter_entries_by_dir()):
 
2598
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2578
2599
                if entry.parent_id is None:
2579
2600
                    continue
2580
2601
                reparent = False
2586
2607
                    kind = file_kind(target_path)
2587
2608
                    if kind == "directory":
2588
2609
                        try:
2589
 
                            bzrdir.BzrDir.open(target_path)
 
2610
                            controldir.ControlDir.open(target_path)
2590
2611
                        except errors.NotBranchError:
2591
2612
                            pass
2592
2613
                        else:
2664
2685
                new_desired_files.append((file_id,
2665
2686
                    (trans_id, tree_path, text_sha1)))
2666
2687
                continue
2667
 
            pb.update('Adding file contents', count + offset, total)
 
2688
            pb.update(gettext('Adding file contents'), count + offset, total)
2668
2689
            if hardlink:
2669
2690
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2670
2691
                                   trans_id)
2691
2712
            contents = filtered_output_bytes(contents, filters,
2692
2713
                ContentFilterContext(tree_path, tree))
2693
2714
        tt.create_file(contents, trans_id, sha1=text_sha1)
2694
 
        pb.update('Adding file contents', count + offset, total)
 
2715
        pb.update(gettext('Adding file contents'), count + offset, total)
2695
2716
 
2696
2717
 
2697
2718
def _reparent_children(tt, old_parent, new_parent):
2811
2832
        tt.set_executability(entry.executable, trans_id)
2812
2833
 
2813
2834
 
2814
 
@deprecated_function(deprecated_in((2, 3, 0)))
2815
 
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2816
 
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2817
 
 
2818
 
 
2819
 
@deprecated_function(deprecated_in((2, 3, 0)))
2820
 
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2821
 
    """Produce a backup-style name that appears to be available"""
2822
 
    def name_gen():
2823
 
        counter = 1
2824
 
        while True:
2825
 
            yield "%s.~%d~" % (name, counter)
2826
 
            counter += 1
2827
 
    for new_name in name_gen():
2828
 
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2829
 
            return new_name
2830
 
 
2831
 
 
2832
 
def _entry_changes(file_id, entry, working_tree):
2833
 
    """Determine in which ways the inventory entry has changed.
2834
 
 
2835
 
    Returns booleans: has_contents, content_mod, meta_mod
2836
 
    has_contents means there are currently contents, but they differ
2837
 
    contents_mod means contents need to be modified
2838
 
    meta_mod means the metadata needs to be modified
2839
 
    """
2840
 
    cur_entry = working_tree.inventory[file_id]
2841
 
    try:
2842
 
        working_kind = working_tree.kind(file_id)
2843
 
        has_contents = True
2844
 
    except NoSuchFile:
2845
 
        has_contents = False
2846
 
        contents_mod = True
2847
 
        meta_mod = False
2848
 
    if has_contents is True:
2849
 
        if entry.kind != working_kind:
2850
 
            contents_mod, meta_mod = True, False
2851
 
        else:
2852
 
            cur_entry._read_tree_state(working_tree.id2path(file_id),
2853
 
                                       working_tree)
2854
 
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
2855
 
            cur_entry._forget_tree_state()
2856
 
    return has_contents, contents_mod, meta_mod
2857
 
 
2858
 
 
2859
2835
def revert(working_tree, target_tree, filenames, backups=False,
2860
2836
           pb=None, change_reporter=None):
2861
2837
    """Revert a working tree's contents to those of a target tree."""
3038
3014
    pb = ui.ui_factory.nested_progress_bar()
3039
3015
    try:
3040
3016
        for n in range(10):
3041
 
            pb.update('Resolution pass', n+1, 10)
 
3017
            pb.update(gettext('Resolution pass'), n+1, 10)
3042
3018
            conflicts = tt.find_conflicts()
3043
3019
            if len(conflicts) == 0:
3044
3020
                return new_conflicts
3068
3044
                existing_file, new_file = conflict[2], conflict[1]
3069
3045
            else:
3070
3046
                existing_file, new_file = conflict[1], conflict[2]
3071
 
            new_name = tt.final_name(existing_file)+'.moved'
 
3047
            new_name = tt.final_name(existing_file) + '.moved'
3072
3048
            tt.adjust_path(new_name, final_parent, existing_file)
3073
3049
            new_conflicts.add((c_type, 'Moved existing file to',
3074
3050
                               existing_file, new_file))