~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Robert Collins
  • Date: 2009-05-11 01:59:06 UTC
  • mto: This revision was merged to the branch mainline in revision 4593.
  • Revision ID: robertc@robertcollins.net-20090511015906-6zi6a9b8tuuhipc8
Less lock thrashing in check.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
48
48
import itertools
49
49
import operator
50
50
import stat
 
51
from time import time
 
52
import warnings
51
53
import re
52
54
 
53
55
import bzrlib
55
57
    branch,
56
58
    bzrdir,
57
59
    conflicts as _mod_conflicts,
 
60
    dirstate,
58
61
    errors,
59
62
    generate_ids,
60
63
    globbing,
61
 
    graph as _mod_graph,
62
64
    hashcache,
63
65
    ignores,
64
 
    inventory,
65
66
    merge,
66
67
    revision as _mod_revision,
67
68
    revisiontree,
 
69
    repository,
68
70
    textui,
69
71
    trace,
70
72
    transform,
71
73
    ui,
 
74
    urlutils,
72
75
    views,
73
76
    xml5,
 
77
    xml6,
74
78
    xml7,
75
79
    )
76
80
import bzrlib.branch
77
81
from bzrlib.transport import get_transport
 
82
import bzrlib.ui
78
83
from bzrlib.workingtree_4 import (
79
84
    WorkingTreeFormat4,
80
85
    WorkingTreeFormat5,
84
89
 
85
90
from bzrlib import symbol_versioning
86
91
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
92
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, TreeReference
87
93
from bzrlib.lockable_files import LockableFiles
88
94
from bzrlib.lockdir import LockDir
89
95
import bzrlib.mutabletree
90
96
from bzrlib.mutabletree import needs_tree_write_lock
91
97
from bzrlib import osutils
92
98
from bzrlib.osutils import (
 
99
    compact_date,
93
100
    file_kind,
94
101
    isdir,
95
102
    normpath,
96
103
    pathjoin,
 
104
    rand_chars,
97
105
    realpath,
98
106
    safe_unicode,
99
107
    splitpath,
103
111
from bzrlib.trace import mutter, note
104
112
from bzrlib.transport.local import LocalTransport
105
113
from bzrlib.progress import DummyProgress, ProgressPhase
106
 
from bzrlib.revision import CURRENT_REVISION
 
114
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
107
115
from bzrlib.rio import RioReader, rio_file, Stanza
108
 
from bzrlib.symbol_versioning import (
109
 
    deprecated_passed,
110
 
    DEPRECATED_PARAMETER,
111
 
    )
 
116
from bzrlib.symbol_versioning import (deprecated_passed,
 
117
        deprecated_method,
 
118
        deprecated_function,
 
119
        DEPRECATED_PARAMETER,
 
120
        )
112
121
 
113
122
 
114
123
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
447
456
 
448
457
    def get_file_with_stat(self, file_id, path=None, filtered=True,
449
458
        _fstat=os.fstat):
450
 
        """See Tree.get_file_with_stat."""
 
459
        """See MutableTree.get_file_with_stat."""
451
460
        if path is None:
452
461
            path = self.id2path(file_id)
453
462
        file_obj = self.get_file_byname(path, filtered=False)
454
463
        stat_value = _fstat(file_obj.fileno())
455
 
        if filtered and self.supports_content_filtering():
 
464
        if self.supports_content_filtering() and filtered:
456
465
            filters = self._content_filter_stack(path)
457
466
            file_obj = filtered_input_file(file_obj, filters)
458
467
        return (file_obj, stat_value)
463
472
    def get_file_byname(self, filename, filtered=True):
464
473
        path = self.abspath(filename)
465
474
        f = file(path, 'rb')
466
 
        if filtered and self.supports_content_filtering():
 
475
        if self.supports_content_filtering() and filtered:
467
476
            filters = self._content_filter_stack(filename)
468
477
            return filtered_input_file(f, filters)
469
478
        else:
488
497
        incorrectly attributed to CURRENT_REVISION (but after committing, the
489
498
        attribution will be correct).
490
499
        """
491
 
        maybe_file_parent_keys = []
492
 
        for parent_id in self.get_parent_ids():
493
 
            try:
494
 
                parent_tree = self.revision_tree(parent_id)
495
 
            except errors.NoSuchRevisionInTree:
496
 
                parent_tree = self.branch.repository.revision_tree(parent_id)
497
 
            parent_tree.lock_read()
498
 
            try:
499
 
                if file_id not in parent_tree:
500
 
                    continue
501
 
                ie = parent_tree.inventory[file_id]
502
 
                if ie.kind != 'file':
503
 
                    # Note: this is slightly unnecessary, because symlinks and
504
 
                    # directories have a "text" which is the empty text, and we
505
 
                    # know that won't mess up annotations. But it seems cleaner
506
 
                    continue
507
 
                parent_text_key = (file_id, ie.revision)
508
 
                if parent_text_key not in maybe_file_parent_keys:
509
 
                    maybe_file_parent_keys.append(parent_text_key)
510
 
            finally:
511
 
                parent_tree.unlock()
512
 
        graph = _mod_graph.Graph(self.branch.repository.texts)
513
 
        heads = graph.heads(maybe_file_parent_keys)
514
 
        file_parent_keys = []
515
 
        for key in maybe_file_parent_keys:
516
 
            if key in heads:
517
 
                file_parent_keys.append(key)
518
 
 
519
 
        # Now we have the parents of this content
520
 
        annotator = self.branch.repository.texts.get_annotator()
521
 
        text = self.get_file(file_id).read()
522
 
        this_key =(file_id, default_revision)
523
 
        annotator.add_special_text(this_key, file_parent_keys, text)
524
 
        annotations = [(key[-1], line)
525
 
                       for key, line in annotator.annotate_flat(this_key)]
526
 
        return annotations
 
500
        basis = self.basis_tree()
 
501
        basis.lock_read()
 
502
        try:
 
503
            changes = self.iter_changes(basis, True, [self.id2path(file_id)],
 
504
                require_versioned=True).next()
 
505
            changed_content, kind = changes[2], changes[6]
 
506
            if not changed_content:
 
507
                return basis.annotate_iter(file_id)
 
508
            if kind[1] is None:
 
509
                return None
 
510
            import annotate
 
511
            if kind[0] != 'file':
 
512
                old_lines = []
 
513
            else:
 
514
                old_lines = list(basis.annotate_iter(file_id))
 
515
            old = [old_lines]
 
516
            for tree in self.branch.repository.revision_trees(
 
517
                self.get_parent_ids()[1:]):
 
518
                if file_id not in tree:
 
519
                    continue
 
520
                old.append(list(tree.annotate_iter(file_id)))
 
521
            return annotate.reannotate(old, self.get_file(file_id).readlines(),
 
522
                                       default_revision)
 
523
        finally:
 
524
            basis.unlock()
527
525
 
528
526
    def _get_ancestors(self, default_revision):
529
527
        ancestors = set([default_revision])
613
611
 
614
612
    def get_file_size(self, file_id):
615
613
        """See Tree.get_file_size"""
616
 
        # XXX: this returns the on-disk size; it should probably return the
617
 
        # canonical size
618
614
        try:
619
615
            return os.path.getsize(self.id2abspath(file_id))
620
616
        except OSError, e:
751
747
            raise
752
748
        kind = _mapper(stat_result.st_mode)
753
749
        if kind == 'file':
754
 
            return self._file_content_summary(path, stat_result)
 
750
            size = stat_result.st_size
 
751
            # try for a stat cache lookup
 
752
            executable = self._is_executable_from_path_and_stat(path, stat_result)
 
753
            return (kind, size, executable, self._sha_from_stat(
 
754
                path, stat_result))
755
755
        elif kind == 'directory':
756
756
            # perhaps it looks like a plain directory, but it's really a
757
757
            # reference.
764
764
        else:
765
765
            return (kind, None, None, None)
766
766
 
767
 
    def _file_content_summary(self, path, stat_result):
768
 
        size = stat_result.st_size
769
 
        executable = self._is_executable_from_path_and_stat(path, stat_result)
770
 
        # try for a stat cache lookup
771
 
        return ('file', size, executable, self._sha_from_stat(
772
 
            path, stat_result))
773
 
 
774
767
    def _check_parents_for_ghosts(self, revision_ids, allow_leftmost_as_ghost):
775
768
        """Common ghost checking functionality from set_parent_*.
776
769
 
906
899
            branch.last_revision().
907
900
        """
908
901
        from bzrlib.merge import Merger, Merge3Merger
909
 
        pb = ui.ui_factory.nested_progress_bar()
 
902
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
910
903
        try:
911
904
            merger = Merger(self.branch, this_tree=self, pb=pb)
912
905
            merger.pp = ProgressPhase("Merge phase", 5, pb)
1098
1091
            branch.BranchReferenceFormat().initialize(tree_bzrdir, new_branch)
1099
1092
        else:
1100
1093
            tree_bzrdir = branch_bzrdir
1101
 
        wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
 
1094
        wt = tree_bzrdir.create_workingtree(NULL_REVISION)
1102
1095
        wt.set_parent_ids(self.get_parent_ids())
1103
1096
        my_inv = self.inventory
1104
 
        child_inv = inventory.Inventory(root_id=None)
 
1097
        child_inv = Inventory(root_id=None)
1105
1098
        new_root = my_inv[file_id]
1106
1099
        my_inv.remove_recursive_id(file_id)
1107
1100
        new_root.parent_id = None
1132
1125
    def _kind(self, relpath):
1133
1126
        return osutils.file_kind(self.abspath(relpath))
1134
1127
 
1135
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
1136
 
        """List all files as (path, class, kind, id, entry).
 
1128
    def list_files(self, include_root=False):
 
1129
        """Recursively list all files as (path, class, kind, id, entry).
1137
1130
 
1138
1131
        Lists, but does not descend into unversioned directories.
 
1132
 
1139
1133
        This does not include files that have been deleted in this
1140
 
        tree. Skips the control directory.
 
1134
        tree.
1141
1135
 
1142
 
        :param include_root: if True, do not return an entry for the root
1143
 
        :param from_dir: start from this directory or None for the root
1144
 
        :param recursive: whether to recurse into subdirectories or not
 
1136
        Skips the control directory.
1145
1137
        """
1146
1138
        # list_files is an iterator, so @needs_read_lock doesn't work properly
1147
1139
        # with it. So callers should be careful to always read_lock the tree.
1149
1141
            raise errors.ObjectNotLocked(self)
1150
1142
 
1151
1143
        inv = self.inventory
1152
 
        if from_dir is None and include_root is True:
 
1144
        if include_root is True:
1153
1145
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
1154
1146
        # Convert these into local objects to save lookup times
1155
1147
        pathjoin = osutils.pathjoin
1162
1154
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
1163
1155
 
1164
1156
        # directory file_id, relative path, absolute path, reverse sorted children
1165
 
        if from_dir is not None:
1166
 
            from_dir_id = inv.path2id(from_dir)
1167
 
            if from_dir_id is None:
1168
 
                # Directory not versioned
1169
 
                return
1170
 
            from_dir_abspath = pathjoin(self.basedir, from_dir)
1171
 
        else:
1172
 
            from_dir_id = inv.root.file_id
1173
 
            from_dir_abspath = self.basedir
1174
 
        children = os.listdir(from_dir_abspath)
 
1157
        children = os.listdir(self.basedir)
1175
1158
        children.sort()
1176
1159
        # jam 20060527 The kernel sized tree seems equivalent whether we
1177
1160
        # use a deque and popleft to keep them sorted, or if we use a plain
1178
1161
        # list and just reverse() them.
1179
1162
        children = collections.deque(children)
1180
 
        stack = [(from_dir_id, u'', from_dir_abspath, children)]
 
1163
        stack = [(inv.root.file_id, u'', self.basedir, children)]
1181
1164
        while stack:
1182
1165
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
1183
1166
 
1241
1224
                if fk != 'directory':
1242
1225
                    continue
1243
1226
 
1244
 
                # But do this child first if recursing down
1245
 
                if recursive:
1246
 
                    new_children = os.listdir(fap)
1247
 
                    new_children.sort()
1248
 
                    new_children = collections.deque(new_children)
1249
 
                    stack.append((f_ie.file_id, fp, fap, new_children))
1250
 
                    # Break out of inner loop,
1251
 
                    # so that we start outer loop with child
1252
 
                    break
 
1227
                # But do this child first
 
1228
                new_children = os.listdir(fap)
 
1229
                new_children.sort()
 
1230
                new_children = collections.deque(new_children)
 
1231
                stack.append((f_ie.file_id, fp, fap, new_children))
 
1232
                # Break out of inner loop,
 
1233
                # so that we start outer loop with child
 
1234
                break
1253
1235
            else:
1254
1236
                # if we finished all children, pop it off the stack
1255
1237
                stack.pop()
1433
1415
        inv = self.inventory
1434
1416
        for entry in moved:
1435
1417
            try:
1436
 
                self._move_entry(WorkingTree._RenameEntry(
1437
 
                    entry.to_rel, entry.from_id,
 
1418
                self._move_entry(_RenameEntry(entry.to_rel, entry.from_id,
1438
1419
                    entry.to_tail, entry.to_parent_id, entry.from_rel,
1439
1420
                    entry.from_tail, entry.from_parent_id,
1440
1421
                    entry.only_change_inv))
1491
1472
        from_tail = splitpath(from_rel)[-1]
1492
1473
        from_id = inv.path2id(from_rel)
1493
1474
        if from_id is None:
1494
 
            # if file is missing in the inventory maybe it's in the basis_tree
1495
 
            basis_tree = self.branch.basis_tree()
1496
 
            from_id = basis_tree.path2id(from_rel)
1497
 
            if from_id is None:
1498
 
                raise errors.BzrRenameFailedError(from_rel,to_rel,
1499
 
                    errors.NotVersionedError(path=str(from_rel)))
1500
 
            # put entry back in the inventory so we can rename it
1501
 
            from_entry = basis_tree.inventory[from_id].copy()
1502
 
            inv.add(from_entry)
1503
 
        else:
1504
 
            from_entry = inv[from_id]
 
1475
            raise errors.BzrRenameFailedError(from_rel,to_rel,
 
1476
                errors.NotVersionedError(path=str(from_rel)))
 
1477
        from_entry = inv[from_id]
1505
1478
        from_parent_id = from_entry.parent_id
1506
1479
        to_dir, to_tail = os.path.split(to_rel)
1507
1480
        to_dir_id = inv.path2id(to_dir)
1598
1571
 
1599
1572
    @needs_write_lock
1600
1573
    def pull(self, source, overwrite=False, stop_revision=None,
1601
 
             change_reporter=None, possible_transports=None, local=False):
1602
 
        top_pb = ui.ui_factory.nested_progress_bar()
 
1574
             change_reporter=None, possible_transports=None):
 
1575
        top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1603
1576
        source.lock_read()
1604
1577
        try:
1605
1578
            pp = ProgressPhase("Pull phase", 2, top_pb)
1607
1580
            old_revision_info = self.branch.last_revision_info()
1608
1581
            basis_tree = self.basis_tree()
1609
1582
            count = self.branch.pull(source, overwrite, stop_revision,
1610
 
                                     possible_transports=possible_transports,
1611
 
                                     local=local)
 
1583
                                     possible_transports=possible_transports)
1612
1584
            new_revision_info = self.branch.last_revision_info()
1613
1585
            if new_revision_info != old_revision_info:
1614
1586
                pp.next_phase()
1615
1587
                repository = self.branch.repository
1616
 
                pb = ui.ui_factory.nested_progress_bar()
 
1588
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
1617
1589
                basis_tree.lock_read()
1618
1590
                try:
1619
1591
                    new_basis_tree = self.branch.basis_tree()
1679
1651
 
1680
1652
            fl = []
1681
1653
            for subf in os.listdir(dirabs):
1682
 
                if self.bzrdir.is_control_filename(subf):
 
1654
                if subf == '.bzr':
1683
1655
                    continue
1684
1656
                if subf not in dir_entry.children:
1685
1657
                    try:
1898
1870
            firstline = xml.split('\n', 1)[0]
1899
1871
            if (not 'revision_id="' in firstline or
1900
1872
                'format="7"' not in firstline):
1901
 
                inv = self.branch.repository._serializer.read_inventory_from_string(
1902
 
                    xml, new_revision)
 
1873
                inv = self.branch.repository.deserialise_inventory(
 
1874
                    new_revision, xml)
1903
1875
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
1904
1876
            self._write_basis_inventory(xml)
1905
1877
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
2068
2040
            if filenames is None and len(self.get_parent_ids()) > 1:
2069
2041
                parent_trees = []
2070
2042
                last_revision = self.last_revision()
2071
 
                if last_revision != _mod_revision.NULL_REVISION:
 
2043
                if last_revision != NULL_REVISION:
2072
2044
                    if basis_tree is None:
2073
2045
                        basis_tree = self.basis_tree()
2074
2046
                        basis_tree.lock_read()
2112
2084
    def set_inventory(self, new_inventory_list):
2113
2085
        from bzrlib.inventory import (Inventory,
2114
2086
                                      InventoryDirectory,
 
2087
                                      InventoryEntry,
2115
2088
                                      InventoryFile,
2116
2089
                                      InventoryLink)
2117
2090
        inv = Inventory(self.get_root_id())
2410
2383
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
2411
2384
                        ('.bzr', '.bzr'))
2412
2385
                    if (bzrdir_loc < len(cur_disk_dir_content)
2413
 
                        and self.bzrdir.is_control_filename(
2414
 
                            cur_disk_dir_content[bzrdir_loc][0])):
 
2386
                        and cur_disk_dir_content[bzrdir_loc][0] == '.bzr'):
2415
2387
                        # we dont yield the contents of, or, .bzr itself.
2416
2388
                        del cur_disk_dir_content[bzrdir_loc]
2417
2389
            if inv_finished:
2668
2640
 
2669
2641
    def _change_last_revision(self, revision_id):
2670
2642
        """See WorkingTree._change_last_revision."""
2671
 
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
 
2643
        if revision_id is None or revision_id == NULL_REVISION:
2672
2644
            try:
2673
2645
                self._transport.delete('last-revision')
2674
2646
            except errors.NoSuchFile:
2842
2814
        no working tree.  (See bug #43064).
2843
2815
        """
2844
2816
        sio = StringIO()
2845
 
        inv = inventory.Inventory()
 
2817
        inv = Inventory()
2846
2818
        xml5.serializer_v5.write_inventory(inv, sio, working=True)
2847
2819
        sio.seek(0)
2848
2820
        transport.put_file('inventory', sio, file_mode)
2864
2836
            branch.generate_revision_history(revision_id)
2865
2837
        finally:
2866
2838
            branch.unlock()
2867
 
        inv = inventory.Inventory()
 
2839
        inv = Inventory()
2868
2840
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
2869
2841
                         branch,
2870
2842
                         inv,
2987
2959
            # only set an explicit root id if there is one to set.
2988
2960
            if basis_tree.inventory.root is not None:
2989
2961
                wt.set_root_id(basis_tree.get_root_id())
2990
 
            if revision_id == _mod_revision.NULL_REVISION:
 
2962
            if revision_id == NULL_REVISION:
2991
2963
                wt.set_parent_trees([])
2992
2964
            else:
2993
2965
                wt.set_parent_trees([(revision_id, basis_tree)])
3000
2972
        return wt
3001
2973
 
3002
2974
    def _initial_inventory(self):
3003
 
        return inventory.Inventory()
 
2975
        return Inventory()
3004
2976
 
3005
2977
    def __init__(self):
3006
2978
        super(WorkingTreeFormat3, self).__init__()
3035
3007
        return self.get_format_string()
3036
3008
 
3037
3009
 
3038
 
__default_format = WorkingTreeFormat6()
 
3010
__default_format = WorkingTreeFormat4()
3039
3011
WorkingTreeFormat.register_format(__default_format)
 
3012
WorkingTreeFormat.register_format(WorkingTreeFormat6())
3040
3013
WorkingTreeFormat.register_format(WorkingTreeFormat5())
3041
 
WorkingTreeFormat.register_format(WorkingTreeFormat4())
3042
3014
WorkingTreeFormat.register_format(WorkingTreeFormat3())
3043
3015
WorkingTreeFormat.set_default_format(__default_format)
3044
3016
# formats which have no format string are not discoverable