~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

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