~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

'bzr selftest' now shows a progress bar with the number of tests, and 
progress made. 'make check' shows tests in -v mode, to be more useful
for the PQM status window. (Robert Collins).

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
from copy import deepcopy
19
19
from cStringIO import StringIO
 
20
import errno
 
21
import os
 
22
import shutil
 
23
import sys
20
24
from unittest import TestSuite
21
25
from warnings import warn
22
26
 
23
27
import bzrlib
24
 
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
25
 
        tree, \
26
 
        ui, \
27
 
        urlutils
 
28
import bzrlib.bzrdir as bzrdir
28
29
from bzrlib.config import TreeConfig
29
30
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
31
from bzrlib.delta import compare_trees
30
32
import bzrlib.errors as errors
31
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
32
 
                           HistoryMissing, InvalidRevisionId, 
33
 
                           InvalidRevisionNumber, LockError, NoSuchFile, 
34
 
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
35
 
                           NotBranchError, UninitializableFormat, 
36
 
                           UnlistableStore, UnlistableBranch, 
37
 
                           )
 
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
35
                           DivergedBranches, LockError,
 
36
                           UninitializableFormat,
 
37
                           UnlistableStore,
 
38
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
39
                           NoWorkingTree)
 
40
import bzrlib.inventory as inventory
 
41
from bzrlib.inventory import Inventory
38
42
from bzrlib.lockable_files import LockableFiles, TransportLock
39
 
from bzrlib.symbol_versioning import (deprecated_function,
40
 
                                      deprecated_method,
41
 
                                      DEPRECATED_PARAMETER,
42
 
                                      deprecated_passed,
43
 
                                      zero_eight, zero_nine,
44
 
                                      )
 
43
from bzrlib.lockdir import LockDir
 
44
from bzrlib.osutils import (isdir, quotefn,
 
45
                            rename, splitpath, sha_file,
 
46
                            file_kind, abspath, normpath, pathjoin,
 
47
                            safe_unicode,
 
48
                            rmtree,
 
49
                            )
 
50
from bzrlib.textui import show_status
45
51
from bzrlib.trace import mutter, note
 
52
from bzrlib.tree import EmptyTree, RevisionTree
 
53
from bzrlib.repository import Repository
 
54
from bzrlib.revision import (
 
55
                             is_ancestor,
 
56
                             NULL_REVISION,
 
57
                             Revision,
 
58
                             )
 
59
from bzrlib.store import copy_all
 
60
from bzrlib.symbol_versioning import *
 
61
import bzrlib.transactions as transactions
 
62
from bzrlib.transport import Transport, get_transport
 
63
from bzrlib.tree import EmptyTree, RevisionTree
 
64
import bzrlib.ui
 
65
import bzrlib.xml5
46
66
 
47
67
 
48
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
97
117
        
98
118
    @staticmethod
99
119
    def open(base, _unsupported=False):
100
 
        """Open the branch rooted at base.
 
120
        """Open the repository rooted at base.
101
121
 
102
 
        For instance, if the branch is at URL/.bzr/branch,
103
 
        Branch.open(URL) -> a Branch instance.
 
122
        For instance, if the repository is at URL/.bzr/repository,
 
123
        Repository.open(URL) -> a Repository instance.
104
124
        """
105
125
        control = bzrdir.BzrDir.open(base, _unsupported)
106
126
        return control.open_branch(_unsupported)
138
158
        warn('%s is deprecated' % self.setup_caching)
139
159
        self.cache_root = cache_root
140
160
 
141
 
    def get_config(self):
142
 
        return bzrlib.config.BranchConfig(self)
143
 
 
144
161
    def _get_nick(self):
145
 
        return self.get_config().get_nickname()
 
162
        cfg = self.tree_config()
 
163
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
146
164
 
147
165
    def _set_nick(self, nick):
148
 
        self.get_config().set_user_option('nickname', nick)
 
166
        cfg = self.tree_config()
 
167
        cfg.set_option(nick, "nickname")
 
168
        assert cfg.get_option("nickname") == nick
149
169
 
150
170
    nick = property(_get_nick, _set_nick)
151
171
 
199
219
        if self.base == from_branch.base:
200
220
            return (0, [])
201
221
        if pb is None:
202
 
            nested_pb = ui.ui_factory.nested_progress_bar()
 
222
            nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
203
223
            pb = nested_pb
204
224
        else:
205
225
            nested_pb = None
213
233
                    last_revision = from_history[-1]
214
234
                else:
215
235
                    # no history in the source branch
216
 
                    last_revision = revision.NULL_REVISION
 
236
                    last_revision = NULL_REVISION
217
237
            return self.repository.fetch(from_branch.repository,
218
238
                                         revision_id=last_revision,
219
239
                                         pb=nested_pb)
229
249
        branch.
230
250
        """
231
251
        return None
232
 
    
233
 
    def get_commit_builder(self, parents, config=None, timestamp=None, 
234
 
                           timezone=None, committer=None, revprops=None, 
235
 
                           revision_id=None):
236
 
        """Obtain a CommitBuilder for this branch.
237
 
        
238
 
        :param parents: Revision ids of the parents of the new revision.
239
 
        :param config: Optional configuration to use.
240
 
        :param timestamp: Optional timestamp recorded for commit.
241
 
        :param timezone: Optional timezone for timestamp.
242
 
        :param committer: Optional committer to set for commit.
243
 
        :param revprops: Optional dictionary of revision properties.
244
 
        :param revision_id: Optional revision id.
245
 
        """
246
 
 
247
 
        if config is None:
248
 
            config = self.get_config()
249
 
        
250
 
        return self.repository.get_commit_builder(self, parents, config, 
251
 
            timestamp, timezone, committer, revprops, revision_id)
252
252
 
253
253
    def get_master_branch(self):
254
254
        """Return the branch we are bound to.
257
257
        """
258
258
        return None
259
259
 
260
 
    def get_revision_delta(self, revno):
261
 
        """Return the delta for one revision.
262
 
 
263
 
        The delta is relative to its mainline predecessor, or the
264
 
        empty tree for revision 1.
265
 
        """
266
 
        assert isinstance(revno, int)
267
 
        rh = self.revision_history()
268
 
        if not (1 <= revno <= len(rh)):
269
 
            raise InvalidRevisionNumber(revno)
270
 
        return self.repository.get_revision_delta(rh[revno-1])
271
 
 
272
260
    def get_root_id(self):
273
261
        """Return the id of this branches root"""
274
262
        raise NotImplementedError('get_root_id is abstract')
312
300
        
313
301
        If self and other have not diverged, return a list of the revisions
314
302
        present in other, but missing from self.
 
303
 
 
304
        >>> from bzrlib.workingtree import WorkingTree
 
305
        >>> bzrlib.trace.silent = True
 
306
        >>> d1 = bzrdir.ScratchDir()
 
307
        >>> br1 = d1.open_branch()
 
308
        >>> wt1 = d1.open_workingtree()
 
309
        >>> d2 = bzrdir.ScratchDir()
 
310
        >>> br2 = d2.open_branch()
 
311
        >>> wt2 = d2.open_workingtree()
 
312
        >>> br1.missing_revisions(br2)
 
313
        []
 
314
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
 
315
        >>> br1.missing_revisions(br2)
 
316
        [u'REVISION-ID-1']
 
317
        >>> br2.missing_revisions(br1)
 
318
        []
 
319
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
 
320
        >>> br1.missing_revisions(br2)
 
321
        []
 
322
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
 
323
        >>> br1.missing_revisions(br2)
 
324
        [u'REVISION-ID-2A']
 
325
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
 
326
        >>> br1.missing_revisions(br2)
 
327
        Traceback (most recent call last):
 
328
        DivergedBranches: These branches have diverged.  Try merge.
315
329
        """
316
330
        self_history = self.revision_history()
317
331
        self_len = len(self_history)
327
341
        else:
328
342
            assert isinstance(stop_revision, int)
329
343
            if stop_revision > other_len:
330
 
                raise errors.NoSuchRevision(self, stop_revision)
 
344
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
331
345
        return other_history[self_len:stop_revision]
332
346
 
333
347
    def update_revisions(self, other, stop_revision=None):
401
415
        """
402
416
        raise NotImplementedError('get_parent is abstract')
403
417
 
404
 
    def get_submit_branch(self):
405
 
        """Return the submit location of the branch.
406
 
 
407
 
        This is the default location for bundle.  The usual
408
 
        pattern is that the user can override it by specifying a
409
 
        location.
410
 
        """
411
 
        return self.get_config().get_user_option('submit_branch')
412
 
 
413
 
    def set_submit_branch(self, location):
414
 
        """Return the submit location of the branch.
415
 
 
416
 
        This is the default location for bundle.  The usual
417
 
        pattern is that the user can override it by specifying a
418
 
        location.
419
 
        """
420
 
        self.get_config().set_user_option('submit_branch', location)
421
 
 
422
418
    def get_push_location(self):
423
419
        """Return the None or the location to push this branch to."""
424
420
        raise NotImplementedError('get_push_location is abstract')
461
457
        revision_id: if not None, the revision history in the new branch will
462
458
                     be truncated to end with revision_id.
463
459
        """
464
 
        # for API compatibility, until 0.8 releases we provide the old api:
 
460
        # for API compatability, until 0.8 releases we provide the old api:
465
461
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
466
462
        # after 0.8 releases, the *args and **kwargs should be changed:
467
463
        # def clone(self, to_bzrdir, revision_id=None):
469
465
            kwargs.get('revision', None) or
470
466
            kwargs.get('basis_branch', None) or
471
467
            (len(args) and isinstance(args[0], basestring))):
472
 
            # backwards compatibility api:
 
468
            # backwards compatability api:
473
469
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
474
470
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
475
471
            # get basis_branch
541
537
        if parent:
542
538
            destination.set_parent(parent)
543
539
 
544
 
    @needs_read_lock
545
 
    def check(self):
546
 
        """Check consistency of the branch.
547
 
 
548
 
        In particular this checks that revisions given in the revision-history
549
 
        do actually match up in the revision graph, and that they're all 
550
 
        present in the repository.
551
 
        
552
 
        Callers will typically also want to check the repository.
553
 
 
554
 
        :return: A BranchCheckResult.
555
 
        """
556
 
        mainline_parent_id = None
557
 
        for revision_id in self.revision_history():
558
 
            try:
559
 
                revision = self.repository.get_revision(revision_id)
560
 
            except errors.NoSuchRevision, e:
561
 
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
562
 
                            % revision_id)
563
 
            # In general the first entry on the revision history has no parents.
564
 
            # But it's not illegal for it to have parents listed; this can happen
565
 
            # in imports from Arch when the parents weren't reachable.
566
 
            if mainline_parent_id is not None:
567
 
                if mainline_parent_id not in revision.parent_ids:
568
 
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
569
 
                                        "parents of {%s}"
570
 
                                        % (mainline_parent_id, revision_id))
571
 
            mainline_parent_id = revision_id
572
 
        return BranchCheckResult(self)
573
 
 
574
540
 
575
541
class BranchFormat(object):
576
542
    """An encapsulation of the initialization and open routines for a format.
606
572
        except NoSuchFile:
607
573
            raise NotBranchError(path=transport.base)
608
574
        except KeyError:
609
 
            raise errors.UnknownFormatError(format=format_string)
 
575
            raise errors.UnknownFormatError(format_string)
610
576
 
611
577
    @classmethod
612
578
    def get_default_format(klass):
623
589
 
624
590
    def initialize(self, a_bzrdir):
625
591
        """Create a branch of this format in a_bzrdir."""
626
 
        raise NotImplementedError(self.initialize)
 
592
        raise NotImplementedError(self.initialized)
627
593
 
628
594
    def is_supported(self):
629
595
        """Is this format supported?
739
705
        utf8_files = [('revision-history', ''),
740
706
                      ('branch-name', ''),
741
707
                      ]
742
 
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
 
708
        control_files = LockableFiles(branch_transport, 'lock', LockDir)
743
709
        control_files.create_lock()
744
710
        control_files.lock_write()
745
711
        control_files.put_utf8('format', self.get_format_string())
764
730
            format = BranchFormat.find_format(a_bzrdir)
765
731
            assert format.__class__ == self.__class__
766
732
        transport = a_bzrdir.get_branch_transport(None)
767
 
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
 
733
        control_files = LockableFiles(transport, 'lock', LockDir)
768
734
        return BzrBranch5(_format=self,
769
735
                          _control_files=control_files,
770
736
                          a_bzrdir=a_bzrdir,
885
851
        self._base = self._transport.base
886
852
        self._format = _format
887
853
        if _control_files is None:
888
 
            raise ValueError('BzrBranch _control_files is None')
 
854
            raise BzrBadParameterMissing('_control_files')
889
855
        self.control_files = _control_files
890
856
        if deprecated_passed(init):
891
857
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
906
872
                 stacklevel=2)
907
873
            if (not relax_version_check
908
874
                and not self._format.is_supported()):
909
 
                raise errors.UnsupportedFormatError(format=fmt)
 
875
                raise errors.UnsupportedFormatError(
 
876
                        'sorry, branch format %r not supported' % fmt,
 
877
                        ['use a different bzr version',
 
878
                         'or remove the .bzr directory'
 
879
                         ' and "bzr init" again'])
910
880
        if deprecated_passed(transport):
911
881
            warn("BzrBranch.__init__(transport=XXX...): The transport "
912
882
                 "parameter is deprecated as of bzr 0.8. "
930
900
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
931
901
        if hasattr(self, 'cache_root') and self.cache_root is not None:
932
902
            try:
933
 
                osutils.rmtree(self.cache_root)
 
903
                rmtree(self.cache_root)
934
904
            except:
935
905
                pass
936
906
            self.cache_root = None
979
949
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
980
950
        """
981
951
        if format is None:
982
 
            format = BranchFormat.find_format(self.bzrdir)
 
952
            format = BzrBranchFormat.find_format(self.bzrdir)
983
953
        self._format = format
984
954
        mutter("got branch format %s", self._format)
985
955
 
1051
1021
            # not really an object yet, and the transaction is for objects.
1052
1022
            # transaction.register_clean(history)
1053
1023
 
 
1024
    def get_revision_delta(self, revno):
 
1025
        """Return the delta for one revision.
 
1026
 
 
1027
        The delta is relative to its mainline predecessor, or the
 
1028
        empty tree for revision 1.
 
1029
        """
 
1030
        assert isinstance(revno, int)
 
1031
        rh = self.revision_history()
 
1032
        if not (1 <= revno <= len(rh)):
 
1033
            raise InvalidRevisionNumber(revno)
 
1034
 
 
1035
        # revno is 1-based; list is 0-based
 
1036
 
 
1037
        new_tree = self.repository.revision_tree(rh[revno-1])
 
1038
        if revno == 1:
 
1039
            old_tree = EmptyTree()
 
1040
        else:
 
1041
            old_tree = self.repository.revision_tree(rh[revno-2])
 
1042
        return compare_trees(old_tree, new_tree)
 
1043
 
1054
1044
    @needs_read_lock
1055
1045
    def revision_history(self):
1056
1046
        """See Branch.revision_history."""
1068
1058
        return list(history)
1069
1059
 
1070
1060
    @needs_write_lock
1071
 
    def generate_revision_history(self, revision_id, last_rev=None, 
1072
 
        other_branch=None):
1073
 
        """Create a new revision history that will finish with revision_id.
1074
 
        
1075
 
        :param revision_id: the new tip to use.
1076
 
        :param last_rev: The previous last_revision. If not None, then this
1077
 
            must be a ancestory of revision_id, or DivergedBranches is raised.
1078
 
        :param other_branch: The other branch that DivergedBranches should
1079
 
            raise with respect to.
1080
 
        """
1081
 
        # stop_revision must be a descendant of last_revision
1082
 
        stop_graph = self.repository.get_revision_graph(revision_id)
1083
 
        if last_rev is not None and last_rev not in stop_graph:
1084
 
            # our previous tip is not merged into stop_revision
1085
 
            raise errors.DivergedBranches(self, other_branch)
1086
 
        # make a new revision history from the graph
1087
 
        current_rev_id = revision_id
1088
 
        new_history = []
1089
 
        while current_rev_id not in (None, revision.NULL_REVISION):
1090
 
            new_history.append(current_rev_id)
1091
 
            current_rev_id_parents = stop_graph[current_rev_id]
1092
 
            try:
1093
 
                current_rev_id = current_rev_id_parents[0]
1094
 
            except IndexError:
1095
 
                current_rev_id = None
1096
 
        new_history.reverse()
1097
 
        self.set_revision_history(new_history)
1098
 
 
1099
 
    @needs_write_lock
1100
1061
    def update_revisions(self, other, stop_revision=None):
1101
1062
        """See Branch.update_revisions."""
1102
1063
        other.lock_read()
1116
1077
            if stop_revision in my_ancestry:
1117
1078
                # last_revision is a descendant of stop_revision
1118
1079
                return
1119
 
            self.generate_revision_history(stop_revision, last_rev=last_rev,
1120
 
                other_branch=other)
 
1080
            # stop_revision must be a descendant of last_revision
 
1081
            stop_graph = self.repository.get_revision_graph(stop_revision)
 
1082
            if last_rev is not None and last_rev not in stop_graph:
 
1083
                # our previous tip is not merged into stop_revision
 
1084
                raise errors.DivergedBranches(self, other)
 
1085
            # make a new revision history from the graph
 
1086
            current_rev_id = stop_revision
 
1087
            new_history = []
 
1088
            while current_rev_id not in (None, NULL_REVISION):
 
1089
                new_history.append(current_rev_id)
 
1090
                current_rev_id_parents = stop_graph[current_rev_id]
 
1091
                try:
 
1092
                    current_rev_id = current_rev_id_parents[0]
 
1093
                except IndexError:
 
1094
                    current_rev_id = None
 
1095
            new_history.reverse()
 
1096
            self.set_revision_history(new_history)
1121
1097
        finally:
1122
1098
            other.unlock()
1123
1099
 
1128
1104
    @deprecated_method(zero_eight)
1129
1105
    def working_tree(self):
1130
1106
        """Create a Working tree object for this branch."""
1131
 
 
 
1107
        from bzrlib.workingtree import WorkingTree
1132
1108
        from bzrlib.transport.local import LocalTransport
1133
1109
        if (self.base.find('://') != -1 or 
1134
1110
            not isinstance(self._transport, LocalTransport)):
1155
1131
 
1156
1132
    def get_parent(self):
1157
1133
        """See Branch.get_parent."""
1158
 
 
 
1134
        import errno
1159
1135
        _locs = ['parent', 'pull', 'x-pull']
1160
 
        assert self.base[-1] == '/'
1161
1136
        for l in _locs:
1162
1137
            try:
1163
 
                parent = self.control_files.get(l).read().strip('\n')
 
1138
                return self.control_files.get_utf8(l).read().strip('\n')
1164
1139
            except NoSuchFile:
1165
 
                continue
1166
 
            # This is an old-format absolute path to a local branch
1167
 
            # turn it into a url
1168
 
            if parent.startswith('/'):
1169
 
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
1170
 
            return urlutils.join(self.base[:-1], parent)
 
1140
                pass
1171
1141
        return None
1172
1142
 
1173
1143
    def get_push_location(self):
1174
1144
        """See Branch.get_push_location."""
1175
 
        push_loc = self.get_config().get_user_option('push_location')
 
1145
        config = bzrlib.config.BranchConfig(self)
 
1146
        push_loc = config.get_user_option('push_location')
1176
1147
        return push_loc
1177
1148
 
1178
1149
    def set_push_location(self, location):
1179
1150
        """See Branch.set_push_location."""
1180
 
        self.get_config().set_user_option('push_location', location, 
1181
 
                                          local=True)
 
1151
        config = bzrlib.config.LocationConfig(self.base)
 
1152
        config.set_user_option('push_location', location)
1182
1153
 
1183
1154
    @needs_write_lock
1184
1155
    def set_parent(self, url):
1191
1162
        if url is None:
1192
1163
            self.control_files._transport.delete('parent')
1193
1164
        else:
1194
 
            if isinstance(url, unicode):
1195
 
                try: 
1196
 
                    url = url.encode('ascii')
1197
 
                except UnicodeEncodeError:
1198
 
                    raise bzrlib.errors.InvalidURL(url,
1199
 
                        "Urls must be 7-bit ascii, "
1200
 
                        "use bzrlib.urlutils.escape")
1201
 
                    
1202
 
            url = urlutils.relative_url(self.base, url)
1203
 
            self.control_files.put('parent', url + '\n')
 
1165
            self.control_files.put_utf8('parent', url + '\n')
1204
1166
 
1205
 
    @deprecated_function(zero_nine)
1206
1167
    def tree_config(self):
1207
 
        """DEPRECATED; call get_config instead.  
1208
 
        TreeConfig has become part of BranchConfig."""
1209
1168
        return TreeConfig(self)
1210
1169
 
1211
1170
 
1251
1210
 
1252
1211
        This could memoise the branch, but if thats done
1253
1212
        it must be revalidated on each new lock.
1254
 
        So for now we just don't memoise it.
 
1213
        So for now we just dont memoise it.
1255
1214
        # RBC 20060304 review this decision.
1256
1215
        """
1257
1216
        bound_loc = self.get_bound_location()
1303
1262
        # There may be a different check you could do here
1304
1263
        # rather than actually trying to install revisions remotely.
1305
1264
        # TODO: capture an exception which indicates the remote branch
1306
 
        #       is not writable. 
 
1265
        #       is not writeable. 
1307
1266
        #       If it is up-to-date, this probably should not be a failure
1308
1267
        
1309
1268
        # lock other for write so the revision-history syncing cannot race
1375
1334
        return result
1376
1335
 
1377
1336
 
1378
 
class BranchCheckResult(object):
1379
 
    """Results of checking branch consistency.
1380
 
 
1381
 
    :see: Branch.check
1382
 
    """
1383
 
 
1384
 
    def __init__(self, branch):
1385
 
        self.branch = branch
1386
 
 
1387
 
    def report_results(self, verbose):
1388
 
        """Report the check results via trace.note.
1389
 
        
1390
 
        :param verbose: Requests more detailed display of what was checked,
1391
 
            if any.
1392
 
        """
1393
 
        note('checked branch %s format %s',
1394
 
             self.branch.base,
1395
 
             self.branch._format)
1396
 
 
1397
 
 
1398
1337
######################################################################
1399
1338
# predicates
1400
1339
 
1401
1340
 
1402
1341
@deprecated_function(zero_eight)
 
1342
def ScratchBranch(*args, **kwargs):
 
1343
    """See bzrlib.bzrdir.ScratchDir."""
 
1344
    d = ScratchDir(*args, **kwargs)
 
1345
    return d.open_branch()
 
1346
 
 
1347
 
 
1348
@deprecated_function(zero_eight)
1403
1349
def is_control_file(*args, **kwargs):
1404
1350
    """See bzrlib.workingtree.is_control_file."""
1405
1351
    return bzrlib.workingtree.is_control_file(*args, **kwargs)