~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Vincent Ladeuil
  • Date: 2012-03-13 17:25:29 UTC
  • mfrom: (6499 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6501.
  • Revision ID: v.ladeuil+lp@free.fr-20120313172529-i0suyjnepsor25i7
Merge trunk

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
239
            return
561
566
        for trans_id in self._removed_id:
562
567
            file_id = self.tree_file_id(trans_id)
563
568
            if file_id is not None:
564
 
                # XXX: This seems like something that should go via a different
565
 
                #      indirection.
566
 
                if self._tree.inventory[file_id].kind == 'directory':
 
569
                if self._tree.stored_kind(file_id) == 'directory':
567
570
                    parents.append(trans_id)
568
571
            elif self.tree_kind(trans_id) == 'directory':
569
572
                parents.append(trans_id)
626
629
        for trans_id in self._new_parent:
627
630
            seen = set()
628
631
            parent_id = trans_id
629
 
            while parent_id is not ROOT_PARENT:
 
632
            while parent_id != ROOT_PARENT:
630
633
                seen.add(parent_id)
631
634
                try:
632
635
                    parent_id = self.final_parent(parent_id)
642
645
        """If parent directories are versioned, children must be versioned."""
643
646
        conflicts = []
644
647
        for parent_id, children in by_parent.iteritems():
645
 
            if parent_id is ROOT_PARENT:
 
648
            if parent_id == ROOT_PARENT:
646
649
                continue
647
650
            if self.final_file_id(parent_id) is not None:
648
651
                continue
741
744
        """Children must have a directory parent"""
742
745
        conflicts = []
743
746
        for parent_id, children in by_parent.iteritems():
744
 
            if parent_id is ROOT_PARENT:
 
747
            if parent_id == ROOT_PARENT:
745
748
                continue
746
749
            no_children = True
747
750
            for child_id in children:
763
766
 
764
767
    def _set_executability(self, path, trans_id):
765
768
        """Set the executability of versioned files """
766
 
        if supports_executable():
 
769
        if self._tree._supports_executable():
767
770
            new_executability = self._new_executability[trans_id]
768
771
            abspath = self._tree.abspath(path)
769
772
            current_mode = os.stat(abspath).st_mode
778
781
                    to_mode |= 0010 & ~umask
779
782
            else:
780
783
                to_mode = current_mode & ~0111
781
 
            os.chmod(abspath, to_mode)
 
784
            osutils.chmod_if_possible(abspath, to_mode)
782
785
 
783
786
    def _new_entry(self, name, parent_id, file_id):
784
787
        """Helper function to create a new filesystem entry."""
1233
1236
        finally:
1234
1237
            TreeTransformBase.finalize(self)
1235
1238
 
 
1239
    def _limbo_supports_executable(self):
 
1240
        """Check if the limbo path supports the executable bit."""
 
1241
        # FIXME: Check actual file system capabilities of limbodir
 
1242
        return osutils.supports_executable()
 
1243
 
1236
1244
    def _limbo_name(self, trans_id):
1237
1245
        """Generate the limbo name of a file"""
1238
1246
        limbo_name = self._limbo_files.get(trans_id)
1398
1406
        delete_any(self._limbo_name(trans_id))
1399
1407
 
1400
1408
    def new_orphan(self, trans_id, parent_id):
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)
 
1409
        conf = self._tree.get_config_stack()
 
1410
        handle_orphan = conf.get('bzr.transform.orphan_policy')
1416
1411
        handle_orphan(self, trans_id, parent_id)
1417
1412
 
1418
1413
 
1481
1476
orphaning_registry._set_default_key('conflict')
1482
1477
 
1483
1478
 
 
1479
opt_transform_orphan = _mod_config.RegistryOption(
 
1480
    'bzr.transform.orphan_policy', orphaning_registry,
 
1481
    help='Policy for orphaned files during transform operations.',
 
1482
    invalid='warning')
 
1483
 
 
1484
 
1484
1485
class TreeTransform(DiskTreeTransform):
1485
1486
    """Represent a tree transformation.
1486
1487
 
1557
1558
        try:
1558
1559
            limbodir = urlutils.local_path_from_url(
1559
1560
                tree._transport.abspath('limbo'))
1560
 
            try:
1561
 
                os.mkdir(limbodir)
1562
 
            except OSError, e:
1563
 
                if e.errno == errno.EEXIST:
1564
 
                    raise ExistingLimbo(limbodir)
 
1561
            osutils.ensure_empty_directory_exists(
 
1562
                limbodir,
 
1563
                errors.ExistingLimbo)
1565
1564
            deletiondir = urlutils.local_path_from_url(
1566
1565
                tree._transport.abspath('pending-deletion'))
1567
 
            try:
1568
 
                os.mkdir(deletiondir)
1569
 
            except OSError, e:
1570
 
                if e.errno == errno.EEXIST:
1571
 
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1566
            osutils.ensure_empty_directory_exists(
 
1567
                deletiondir,
 
1568
                errors.ExistingPendingDeletion)
1572
1569
        except:
1573
1570
            tree.unlock()
1574
1571
            raise
1637
1634
            else:
1638
1635
                raise
1639
1636
        if typefunc(mode):
1640
 
            os.chmod(self._limbo_name(trans_id), mode)
 
1637
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1641
1638
 
1642
1639
    def iter_tree_children(self, parent_id):
1643
1640
        """Iterate through the entry's tree children, if any"""
1721
1718
            calculating one.
1722
1719
        :param _mover: Supply an alternate FileMover, for testing
1723
1720
        """
 
1721
        for hook in MutableTree.hooks['pre_transform']:
 
1722
            hook(self._tree, self)
1724
1723
        if not no_conflicts:
1725
1724
            self._check_malformed()
1726
1725
        child_pb = ui.ui_factory.nested_progress_bar()
1727
1726
        try:
1728
1727
            if precomputed_delta is None:
1729
 
                child_pb.update('Apply phase', 0, 2)
 
1728
                child_pb.update(gettext('Apply phase'), 0, 2)
1730
1729
                inventory_delta = self._generate_inventory_delta()
1731
1730
                offset = 1
1732
1731
            else:
1737
1736
            else:
1738
1737
                mover = _mover
1739
1738
            try:
1740
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1739
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1741
1740
                self._apply_removals(mover)
1742
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1741
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1743
1742
                modified_paths = self._apply_insertions(mover)
1744
1743
            except:
1745
1744
                mover.rollback()
1765
1764
        try:
1766
1765
            for num, trans_id in enumerate(self._removed_id):
1767
1766
                if (num % 10) == 0:
1768
 
                    child_pb.update('removing file', num, total_entries)
 
1767
                    child_pb.update(gettext('removing file'), num, total_entries)
1769
1768
                if trans_id == self._new_root:
1770
1769
                    file_id = self._tree.get_root_id()
1771
1770
                else:
1783
1782
            final_kinds = {}
1784
1783
            for num, (path, trans_id) in enumerate(new_paths):
1785
1784
                if (num % 10) == 0:
1786
 
                    child_pb.update('adding file',
 
1785
                    child_pb.update(gettext('adding file'),
1787
1786
                                    num + len(self._removed_id), total_entries)
1788
1787
                file_id = new_path_file_ids[trans_id]
1789
1788
                if file_id is None:
1833
1832
                # do not attempt to move root into a subdirectory of itself.
1834
1833
                if path == '':
1835
1834
                    continue
1836
 
                child_pb.update('removing file', num, len(tree_paths))
 
1835
                child_pb.update(gettext('removing file'), num, len(tree_paths))
1837
1836
                full_path = self._tree.abspath(path)
1838
1837
                if trans_id in self._removed_contents:
1839
1838
                    delete_path = os.path.join(self._deletiondir, trans_id)
1868
1867
        try:
1869
1868
            for num, (path, trans_id) in enumerate(new_paths):
1870
1869
                if (num % 10) == 0:
1871
 
                    child_pb.update('adding file', num, len(new_paths))
 
1870
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1872
1871
                full_path = self._tree.abspath(path)
1873
1872
                if trans_id in self._needs_rename:
1874
1873
                    try:
2057
2056
        pass
2058
2057
 
2059
2058
    @property
 
2059
    @deprecated_method(deprecated_in((2, 5, 0)))
2060
2060
    def inventory(self):
2061
2061
        """This Tree does not use inventory as its backing data."""
2062
2062
        raise NotImplementedError(_PreviewTree.inventory)
2063
2063
 
 
2064
    @property
 
2065
    def root_inventory(self):
 
2066
        """This Tree does not use inventory as its backing data."""
 
2067
        raise NotImplementedError(_PreviewTree.root_inventory)
 
2068
 
2064
2069
    def get_root_id(self):
2065
2070
        return self._transform.final_file_id(self._transform.root)
2066
2071
 
2112
2117
        return cur_parent
2113
2118
 
2114
2119
    def path2id(self, path):
 
2120
        if isinstance(path, list):
 
2121
            if path == []:
 
2122
                path = [""]
 
2123
            path = osutils.pathjoin(*path)
2115
2124
        return self._transform.final_file_id(self._path2trans_id(path))
2116
2125
 
2117
2126
    def id2path(self, file_id):
2258
2267
        else:
2259
2268
            return None
2260
2269
 
 
2270
    def get_file_verifier(self, file_id, path=None, stat_value=None):
 
2271
        trans_id = self._transform.trans_id_file_id(file_id)
 
2272
        kind = self._transform._new_contents.get(trans_id)
 
2273
        if kind is None:
 
2274
            return self._transform._tree.get_file_verifier(file_id)
 
2275
        if kind == 'file':
 
2276
            fileobj = self.get_file(file_id)
 
2277
            try:
 
2278
                return ("SHA1", sha_file(fileobj))
 
2279
            finally:
 
2280
                fileobj.close()
 
2281
 
2261
2282
    def get_file_sha1(self, file_id, path=None, stat_value=None):
2262
2283
        trans_id = self._transform.trans_id_file_id(file_id)
2263
2284
        kind = self._transform._new_contents.get(trans_id)
2313
2334
            if kind == 'file':
2314
2335
                statval = os.lstat(limbo_name)
2315
2336
                size = statval.st_size
2316
 
                if not supports_executable():
 
2337
                if not tt._limbo_supports_executable():
2317
2338
                    executable = False
2318
2339
                else:
2319
2340
                    executable = statval.st_mode & S_IEXEC
2534
2555
    file_trans_id = {}
2535
2556
    top_pb = ui.ui_factory.nested_progress_bar()
2536
2557
    pp = ProgressPhase("Build phase", 2, top_pb)
2537
 
    if tree.inventory.root is not None:
 
2558
    if tree.get_root_id() is not None:
2538
2559
        # This is kind of a hack: we should be altering the root
2539
2560
        # as part of the regular tree shape diff logic.
2540
2561
        # The conditional test here is to avoid doing an
2555
2576
        try:
2556
2577
            deferred_contents = []
2557
2578
            num = 0
2558
 
            total = len(tree.inventory)
 
2579
            total = len(tree.all_file_ids())
2559
2580
            if delta_from_tree:
2560
2581
                precomputed_delta = []
2561
2582
            else:
2570
2591
                for dir, files in wt.walkdirs():
2571
2592
                    existing_files.update(f[0] for f in files)
2572
2593
            for num, (tree_path, entry) in \
2573
 
                enumerate(tree.inventory.iter_entries_by_dir()):
2574
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
2594
                enumerate(tree.iter_entries_by_dir()):
 
2595
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2575
2596
                if entry.parent_id is None:
2576
2597
                    continue
2577
2598
                reparent = False
2583
2604
                    kind = file_kind(target_path)
2584
2605
                    if kind == "directory":
2585
2606
                        try:
2586
 
                            bzrdir.BzrDir.open(target_path)
 
2607
                            controldir.ControlDir.open(target_path)
2587
2608
                        except errors.NotBranchError:
2588
2609
                            pass
2589
2610
                        else:
2661
2682
                new_desired_files.append((file_id,
2662
2683
                    (trans_id, tree_path, text_sha1)))
2663
2684
                continue
2664
 
            pb.update('Adding file contents', count + offset, total)
 
2685
            pb.update(gettext('Adding file contents'), count + offset, total)
2665
2686
            if hardlink:
2666
2687
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2667
2688
                                   trans_id)
2688
2709
            contents = filtered_output_bytes(contents, filters,
2689
2710
                ContentFilterContext(tree_path, tree))
2690
2711
        tt.create_file(contents, trans_id, sha1=text_sha1)
2691
 
        pb.update('Adding file contents', count + offset, total)
 
2712
        pb.update(gettext('Adding file contents'), count + offset, total)
2692
2713
 
2693
2714
 
2694
2715
def _reparent_children(tt, old_parent, new_parent):
2826
2847
            return new_name
2827
2848
 
2828
2849
 
2829
 
def _entry_changes(file_id, entry, working_tree):
2830
 
    """Determine in which ways the inventory entry has changed.
2831
 
 
2832
 
    Returns booleans: has_contents, content_mod, meta_mod
2833
 
    has_contents means there are currently contents, but they differ
2834
 
    contents_mod means contents need to be modified
2835
 
    meta_mod means the metadata needs to be modified
2836
 
    """
2837
 
    cur_entry = working_tree.inventory[file_id]
2838
 
    try:
2839
 
        working_kind = working_tree.kind(file_id)
2840
 
        has_contents = True
2841
 
    except NoSuchFile:
2842
 
        has_contents = False
2843
 
        contents_mod = True
2844
 
        meta_mod = False
2845
 
    if has_contents is True:
2846
 
        if entry.kind != working_kind:
2847
 
            contents_mod, meta_mod = True, False
2848
 
        else:
2849
 
            cur_entry._read_tree_state(working_tree.id2path(file_id),
2850
 
                                       working_tree)
2851
 
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
2852
 
            cur_entry._forget_tree_state()
2853
 
    return has_contents, contents_mod, meta_mod
2854
 
 
2855
 
 
2856
2850
def revert(working_tree, target_tree, filenames, backups=False,
2857
2851
           pb=None, change_reporter=None):
2858
2852
    """Revert a working tree's contents to those of a target tree."""
3035
3029
    pb = ui.ui_factory.nested_progress_bar()
3036
3030
    try:
3037
3031
        for n in range(10):
3038
 
            pb.update('Resolution pass', n+1, 10)
 
3032
            pb.update(gettext('Resolution pass'), n+1, 10)
3039
3033
            conflicts = tt.find_conflicts()
3040
3034
            if len(conflicts) == 0:
3041
3035
                return new_conflicts
3065
3059
                existing_file, new_file = conflict[2], conflict[1]
3066
3060
            else:
3067
3061
                existing_file, new_file = conflict[1], conflict[2]
3068
 
            new_name = tt.final_name(existing_file)+'.moved'
 
3062
            new_name = tt.final_name(existing_file) + '.moved'
3069
3063
            tt.adjust_path(new_name, final_parent, existing_file)
3070
3064
            new_conflicts.add((c_type, 'Moved existing file to',
3071
3065
                               existing_file, new_file))