~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-04-17 00:59:30 UTC
  • mfrom: (1551.15.4 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20070417005930-rofskshyjsfzrahh
Fix ftp transport with servers that don't support atomic rename

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
from bzrlib.lazy_import import lazy_import
21
21
lazy_import(globals(), """
 
22
from copy import deepcopy
 
23
from unittest import TestSuite
22
24
from warnings import warn
23
25
 
24
26
import bzrlib
33
35
        revision as _mod_revision,
34
36
        transport,
35
37
        tree,
36
 
        tsort,
37
38
        ui,
38
39
        urlutils,
39
40
        )
99
100
    def __init__(self, *ignored, **ignored_too):
100
101
        self.tags = self._make_tags()
101
102
        self._revision_history_cache = None
102
 
        self._revision_id_to_revno_cache = None
103
103
 
104
104
    def break_lock(self):
105
105
        """Break a lock if one is present from another instance.
172
172
        return self.get_config().get_nickname()
173
173
 
174
174
    def _set_nick(self, nick):
175
 
        self.get_config().set_user_option('nickname', nick, warn_masked=True)
 
175
        self.get_config().set_user_option('nickname', nick)
176
176
 
177
177
    nick = property(_get_nick, _set_nick)
178
178
 
195
195
    def get_physical_lock_status(self):
196
196
        raise NotImplementedError(self.get_physical_lock_status)
197
197
 
198
 
    @needs_read_lock
199
 
    def get_revision_id_to_revno_map(self):
200
 
        """Return the revision_id => dotted revno map.
201
 
 
202
 
        This will be regenerated on demand, but will be cached.
203
 
 
204
 
        :return: A dictionary mapping revision_id => dotted revno.
205
 
            This dictionary should not be modified by the caller.
206
 
        """
207
 
        if self._revision_id_to_revno_cache is not None:
208
 
            mapping = self._revision_id_to_revno_cache
209
 
        else:
210
 
            mapping = self._gen_revno_map()
211
 
            self._cache_revision_id_to_revno(mapping)
212
 
        # TODO: jam 20070417 Since this is being cached, should we be returning
213
 
        #       a copy?
214
 
        # I would rather not, and instead just declare that users should not
215
 
        # modify the return value.
216
 
        return mapping
217
 
 
218
 
    def _gen_revno_map(self):
219
 
        """Create a new mapping from revision ids to dotted revnos.
220
 
 
221
 
        Dotted revnos are generated based on the current tip in the revision
222
 
        history.
223
 
        This is the worker function for get_revision_id_to_revno_map, which
224
 
        just caches the return value.
225
 
 
226
 
        :return: A dictionary mapping revision_id => dotted revno.
227
 
        """
228
 
        last_revision = self.last_revision()
229
 
        revision_graph = self.repository.get_revision_graph(last_revision)
230
 
        merge_sorted_revisions = tsort.merge_sort(
231
 
            revision_graph,
232
 
            last_revision,
233
 
            None,
234
 
            generate_revno=True)
235
 
        revision_id_to_revno = dict((rev_id, revno)
236
 
                                    for seq_num, rev_id, depth, revno, end_of_merge
237
 
                                     in merge_sorted_revisions)
238
 
        return revision_id_to_revno
239
 
 
240
198
    def leave_lock_in_place(self):
241
199
        """Tell this branch object not to release the physical lock when this
242
200
        object is unlocked.
386
344
        """
387
345
        self._revision_history_cache = rev_history
388
346
 
389
 
    def _cache_revision_id_to_revno(self, revision_id_to_revno):
390
 
        """Set the cached revision_id => revno map to revision_id_to_revno.
391
 
 
392
 
        This API is semi-public; it only for use by subclasses, all other code
393
 
        should consider it to be private.
394
 
        """
395
 
        self._revision_id_to_revno_cache = revision_id_to_revno
396
 
 
397
347
    def _clear_cached_state(self):
398
348
        """Clear any cached data on this branch, e.g. cached revision history.
399
349
 
404
354
        should consider it to be private.
405
355
        """
406
356
        self._revision_history_cache = None
407
 
        self._revision_id_to_revno_cache = None
408
357
 
409
358
    def _gen_revision_history(self):
410
359
        """Return sequence of revision hashes on to this branch.
505
454
 
506
455
    def revision_id_to_revno(self, revision_id):
507
456
        """Given a revision id, return its revno"""
508
 
        if _mod_revision.is_null(revision_id):
 
457
        if revision_id is None:
509
458
            return 0
510
459
        revision_id = osutils.safe_revision_id(revision_id)
511
460
        history = self.revision_history()
512
461
        try:
513
462
            return history.index(revision_id) + 1
514
463
        except ValueError:
515
 
            raise errors.NoSuchRevision(self, revision_id)
 
464
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
516
465
 
517
466
    def get_rev_id(self, revno, history=None):
518
467
        """Find the revision id of the specified revno."""
521
470
        if history is None:
522
471
            history = self.revision_history()
523
472
        if revno <= 0 or revno > len(history):
524
 
            raise errors.NoSuchRevision(self, revno)
 
473
            raise bzrlib.errors.NoSuchRevision(self, revno)
525
474
        return history[revno - 1]
526
475
 
527
476
    def pull(self, source, overwrite=False, stop_revision=None):
584
533
            url = ''
585
534
        elif make_relative:
586
535
            url = urlutils.relative_url(self.base, url)
587
 
        config.set_user_option(name, url, warn_masked=True)
 
536
        config.set_user_option(name, url)
588
537
 
589
538
    def _get_config_location(self, name, config=None):
590
539
        if config is None:
610
559
        pattern is that the user can override it by specifying a
611
560
        location.
612
561
        """
613
 
        self.get_config().set_user_option('submit_branch', location,
614
 
            warn_masked=True)
 
562
        self.get_config().set_user_option('submit_branch', location)
615
563
 
616
564
    def get_public_branch(self):
617
565
        """Return the public location of the branch.
631
579
 
632
580
    def get_push_location(self):
633
581
        """Return the None or the location to push this branch to."""
634
 
        push_loc = self.get_config().get_user_option('push_location')
635
 
        return push_loc
 
582
        raise NotImplementedError(self.get_push_location)
636
583
 
637
584
    def set_push_location(self, location):
638
585
        """Set a new push location for this branch."""
701
648
        :param revision_id: The revision-id to truncate history at.  May
702
649
          be None to copy complete history.
703
650
        """
704
 
        if revision_id == _mod_revision.NULL_REVISION:
705
 
            new_history = []
706
651
        new_history = self.revision_history()
707
 
        if revision_id is not None and new_history != []:
 
652
        if revision_id is not None:
708
653
            revision_id = osutils.safe_revision_id(revision_id)
709
654
            try:
710
655
                new_history = new_history[:new_history.index(revision_id) + 1]
762
707
 
763
708
    def _get_checkout_format(self):
764
709
        """Return the most suitable metadir for a checkout of this branch.
765
 
        Weaves are used if this branch's repository uses weaves.
 
710
        Weaves are used if this branch's repostory uses weaves.
766
711
        """
767
712
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
768
713
            from bzrlib.repofmt import weaverepo
784
729
        :return: The tree of the created checkout
785
730
        """
786
731
        t = transport.get_transport(to_location)
787
 
        t.ensure_base()
 
732
        try:
 
733
            t.mkdir('.')
 
734
        except errors.FileExists:
 
735
            pass
788
736
        if lightweight:
789
737
            format = self._get_checkout_format()
790
738
            checkout = format.initialize_on_transport(t)
848
796
    _formats = {}
849
797
    """The known formats."""
850
798
 
851
 
    def __eq__(self, other):
852
 
        return self.__class__ is other.__class__
853
 
 
854
 
    def __ne__(self, other):
855
 
        return not (self == other)
856
 
 
857
799
    @classmethod
858
800
    def find_format(klass, a_bzrdir):
859
801
        """Return the format for the branch object in a_bzrdir."""
871
813
        """Return the current default format."""
872
814
        return klass._default_format
873
815
 
874
 
    def get_reference(self, a_bzrdir):
875
 
        """Get the target reference of the branch in a_bzrdir.
876
 
 
877
 
        format probing must have been completed before calling
878
 
        this method - it is assumed that the format of the branch
879
 
        in a_bzrdir is correct.
880
 
 
881
 
        :param a_bzrdir: The bzrdir to get the branch data from.
882
 
        :return: None if the branch is not a reference branch.
883
 
        """
884
 
        return None
885
 
 
886
816
    def get_format_string(self):
887
817
        """Return the ASCII format string that identifies this format."""
888
818
        raise NotImplementedError(self.get_format_string)
1004
934
        # (push_result)
1005
935
        # containing the members
1006
936
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
1007
 
        # where local is the local target branch or None, master is the target 
 
937
        # where local is the local branch or None, master is the target 
1008
938
        # master branch, and the rest should be self explanatory. The source
1009
939
        # is read locked and the target branches write locked. Source will
1010
940
        # be the local low-latency branch.
1119
1049
        if not _found:
1120
1050
            format = BranchFormat.find_format(a_bzrdir)
1121
1051
            assert format.__class__ == self.__class__
1122
 
        try:
1123
 
            transport = a_bzrdir.get_branch_transport(None)
1124
 
            control_files = lockable_files.LockableFiles(transport, 'lock',
1125
 
                                                         lockdir.LockDir)
1126
 
            return BzrBranch5(_format=self,
1127
 
                              _control_files=control_files,
1128
 
                              a_bzrdir=a_bzrdir,
1129
 
                              _repository=a_bzrdir.find_repository())
1130
 
        except NoSuchFile:
1131
 
            raise NotBranchError(path=transport.base)
 
1052
        transport = a_bzrdir.get_branch_transport(None)
 
1053
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1054
                                                     lockdir.LockDir)
 
1055
        return BzrBranch5(_format=self,
 
1056
                          _control_files=control_files,
 
1057
                          a_bzrdir=a_bzrdir,
 
1058
                          _repository=a_bzrdir.find_repository())
1132
1059
 
1133
1060
 
1134
1061
class BzrBranchFormat6(BzrBranchFormat5):
1198
1125
        """See BranchFormat.get_format_description()."""
1199
1126
        return "Checkout reference format 1"
1200
1127
        
1201
 
    def get_reference(self, a_bzrdir):
1202
 
        """See BranchFormat.get_reference()."""
1203
 
        transport = a_bzrdir.get_branch_transport(None)
1204
 
        return transport.get('location').read()
1205
 
 
1206
1128
    def initialize(self, a_bzrdir, target_branch=None):
1207
1129
        """Create a branch of this format in a_bzrdir."""
1208
1130
        if target_branch is None:
1230
1152
            # emit some sort of warning/error to the caller ?!
1231
1153
        return clone
1232
1154
 
1233
 
    def open(self, a_bzrdir, _found=False, location=None):
 
1155
    def open(self, a_bzrdir, _found=False):
1234
1156
        """Return the branch that the branch reference in a_bzrdir points at.
1235
1157
 
1236
1158
        _found is a private parameter, do not use it. It is used to indicate
1239
1161
        if not _found:
1240
1162
            format = BranchFormat.find_format(a_bzrdir)
1241
1163
            assert format.__class__ == self.__class__
1242
 
        if location is None:
1243
 
            location = self.get_reference(a_bzrdir)
1244
 
        real_bzrdir = bzrdir.BzrDir.open(location)
 
1164
        transport = a_bzrdir.get_branch_transport(None)
 
1165
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
1245
1166
        result = real_bzrdir.open_branch()
1246
1167
        # this changes the behaviour of result.clone to create a new reference
1247
1168
        # rather than a copy of the content of the branch.
1382
1303
    def set_revision_history(self, rev_history):
1383
1304
        """See Branch.set_revision_history."""
1384
1305
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1385
 
        self._clear_cached_state()
1386
1306
        self._write_revision_history(rev_history)
1387
1307
        self._cache_revision_history(rev_history)
1388
1308
        for hook in Branch.hooks['set_rh']:
1406
1326
                          other_branch=None):
1407
1327
        # stop_revision must be a descendant of last_revision
1408
1328
        stop_graph = self.repository.get_revision_graph(revision_id)
1409
 
        if (last_rev is not None and last_rev != _mod_revision.NULL_REVISION
1410
 
            and last_rev not in stop_graph):
 
1329
        if last_rev is not None and last_rev not in stop_graph:
1411
1330
            # our previous tip is not merged into stop_revision
1412
1331
            raise errors.DivergedBranches(self, other_branch)
1413
1332
        # make a new revision history from the graph
1452
1371
                stop_revision = osutils.safe_revision_id(stop_revision)
1453
1372
            # whats the current last revision, before we fetch [and change it
1454
1373
            # possibly]
1455
 
            last_rev = _mod_revision.ensure_null(self.last_revision())
 
1374
            last_rev = self.last_revision()
1456
1375
            # we fetch here regardless of whether we need to so that we pickup
1457
1376
            # filled in ghosts.
1458
1377
            self.fetch(other, stop_revision)
1459
 
            my_ancestry = self.repository.get_ancestry(last_rev,
1460
 
                                                       topo_sorted=False)
 
1378
            my_ancestry = self.repository.get_ancestry(last_rev)
1461
1379
            if stop_revision in my_ancestry:
1462
1380
                # last_revision is a descendant of stop_revision
1463
1381
                return
1482
1400
 
1483
1401
    @needs_write_lock
1484
1402
    def pull(self, source, overwrite=False, stop_revision=None,
1485
 
             _hook_master=None, run_hooks=True):
 
1403
        _hook_master=None, _run_hooks=True):
1486
1404
        """See Branch.pull.
1487
1405
 
1488
1406
        :param _hook_master: Private parameter - set the branch to 
1489
1407
            be supplied as the master to push hooks.
1490
 
        :param run_hooks: Private parameter - if false, this branch
1491
 
            is being called because it's the master of the primary branch,
1492
 
            so it should not run its hooks.
 
1408
        :param _run_hooks: Private parameter - allow disabling of
 
1409
            hooks, used when pushing to a master branch.
1493
1410
        """
1494
1411
        result = PullResult()
1495
1412
        result.source_branch = source
1514
1431
            else:
1515
1432
                result.master_branch = self
1516
1433
                result.local_branch = None
1517
 
            if run_hooks:
 
1434
            if _run_hooks:
1518
1435
                for hook in Branch.hooks['post_pull']:
1519
1436
                    hook(result)
1520
1437
        finally:
1532
1449
 
1533
1450
    @needs_read_lock
1534
1451
    def push(self, target, overwrite=False, stop_revision=None,
1535
 
             _override_hook_source_branch=None):
 
1452
        _hook_master=None, _run_hooks=True):
1536
1453
        """See Branch.push.
1537
 
 
1538
 
        This is the basic concrete implementation of push()
1539
 
 
1540
 
        :param _override_hook_source_branch: If specified, run
1541
 
        the hooks passing this Branch as the source, rather than self.  
1542
 
        This is for use of RemoteBranch, where push is delegated to the
1543
 
        underlying vfs-based Branch. 
1544
 
        """
1545
 
        # TODO: Public option to disable running hooks - should be trivial but
1546
 
        # needs tests.
1547
 
        target.lock_write()
1548
 
        try:
1549
 
            result = self._push_with_bound_branches(target, overwrite,
1550
 
                    stop_revision,
1551
 
                    _override_hook_source_branch=_override_hook_source_branch)
1552
 
            return result
1553
 
        finally:
1554
 
            target.unlock()
1555
 
 
1556
 
    def _push_with_bound_branches(self, target, overwrite,
1557
 
            stop_revision,
1558
 
            _override_hook_source_branch=None):
1559
 
        """Push from self into target, and into target's master if any.
1560
1454
        
1561
 
        This is on the base BzrBranch class even though it doesn't support 
1562
 
        bound branches because the *target* might be bound.
1563
 
        """
1564
 
        def _run_hooks():
1565
 
            if _override_hook_source_branch:
1566
 
                result.source_branch = _override_hook_source_branch
1567
 
            for hook in Branch.hooks['post_push']:
1568
 
                hook(result)
1569
 
 
1570
 
        bound_location = target.get_bound_location()
1571
 
        if bound_location and target.base != bound_location:
1572
 
            # there is a master branch.
1573
 
            #
1574
 
            # XXX: Why the second check?  Is it even supported for a branch to
1575
 
            # be bound to itself? -- mbp 20070507
1576
 
            master_branch = target.get_master_branch()
1577
 
            master_branch.lock_write()
1578
 
            try:
1579
 
                # push into the master from this branch.
1580
 
                self._basic_push(master_branch, overwrite, stop_revision)
1581
 
                # and push into the target branch from this. Note that we push from
1582
 
                # this branch again, because its considered the highest bandwidth
1583
 
                # repository.
1584
 
                result = self._basic_push(target, overwrite, stop_revision)
1585
 
                result.master_branch = master_branch
1586
 
                result.local_branch = target
1587
 
                _run_hooks()
1588
 
                return result
1589
 
            finally:
1590
 
                master_branch.unlock()
1591
 
        else:
1592
 
            # no master branch
1593
 
            result = self._basic_push(target, overwrite, stop_revision)
1594
 
            # TODO: Why set master_branch and local_branch if there's no
1595
 
            # binding?  Maybe cleaner to just leave them unset? -- mbp
1596
 
            # 20070504
1597
 
            result.master_branch = target
1598
 
            result.local_branch = None
1599
 
            _run_hooks()
1600
 
            return result
1601
 
 
1602
 
    def _basic_push(self, target, overwrite, stop_revision):
1603
 
        """Basic implementation of push without bound branches or hooks.
1604
 
 
1605
 
        Must be called with self read locked and target write locked.
 
1455
        :param _hook_master: Private parameter - set the branch to 
 
1456
            be supplied as the master to push hooks.
 
1457
        :param _run_hooks: Private parameter - allow disabling of
 
1458
            hooks, used when pushing to a master branch.
1606
1459
        """
1607
1460
        result = PushResult()
1608
1461
        result.source_branch = self
1609
1462
        result.target_branch = target
1610
 
        result.old_revno, result.old_revid = target.last_revision_info()
 
1463
        target.lock_write()
1611
1464
        try:
1612
 
            target.update_revisions(self, stop_revision)
1613
 
        except DivergedBranches:
1614
 
            if not overwrite:
1615
 
                raise
1616
 
        if overwrite:
1617
 
            target.set_revision_history(self.revision_history())
1618
 
        result.tag_conflicts = self.tags.merge_to(target.tags)
1619
 
        result.new_revno, result.new_revid = target.last_revision_info()
 
1465
            result.old_revno, result.old_revid = target.last_revision_info()
 
1466
            try:
 
1467
                target.update_revisions(self, stop_revision)
 
1468
            except DivergedBranches:
 
1469
                if not overwrite:
 
1470
                    raise
 
1471
            if overwrite:
 
1472
                target.set_revision_history(self.revision_history())
 
1473
            result.tag_conflicts = self.tags.merge_to(target.tags)
 
1474
            result.new_revno, result.new_revid = target.last_revision_info()
 
1475
            if _hook_master:
 
1476
                result.master_branch = _hook_master
 
1477
                result.local_branch = target
 
1478
            else:
 
1479
                result.master_branch = target
 
1480
                result.local_branch = None
 
1481
            if _run_hooks:
 
1482
                for hook in Branch.hooks['post_push']:
 
1483
                    hook(result)
 
1484
        finally:
 
1485
            target.unlock()
1620
1486
        return result
1621
1487
 
1622
1488
    def get_parent(self):
1635
1501
        except errors.InvalidURLJoin, e:
1636
1502
            raise errors.InaccessibleParent(parent, self.base)
1637
1503
 
 
1504
    def get_push_location(self):
 
1505
        """See Branch.get_push_location."""
 
1506
        push_loc = self.get_config().get_user_option('push_location')
 
1507
        return push_loc
 
1508
 
1638
1509
    def set_push_location(self, location):
1639
1510
        """See Branch.set_push_location."""
1640
1511
        self.get_config().set_user_option(
1654
1525
                try: 
1655
1526
                    url = url.encode('ascii')
1656
1527
                except UnicodeEncodeError:
1657
 
                    raise errors.InvalidURL(url,
 
1528
                    raise bzrlib.errors.InvalidURL(url,
1658
1529
                        "Urls must be 7-bit ascii, "
1659
1530
                        "use bzrlib.urlutils.escape")
1660
1531
            url = urlutils.relative_url(self.base, url)
1692
1563
        
1693
1564
    @needs_write_lock
1694
1565
    def pull(self, source, overwrite=False, stop_revision=None,
1695
 
             run_hooks=True):
1696
 
        """Pull from source into self, updating my master if any.
 
1566
        _run_hooks=True):
 
1567
        """Extends branch.pull to be bound branch aware.
1697
1568
        
1698
 
        :param run_hooks: Private parameter - if false, this branch
1699
 
            is being called because it's the master of the primary branch,
1700
 
            so it should not run its hooks.
 
1569
        :param _run_hooks: Private parameter used to force hook running
 
1570
            off during bound branch double-pushing.
1701
1571
        """
1702
1572
        bound_location = self.get_bound_location()
1703
1573
        master_branch = None
1709
1579
            if master_branch:
1710
1580
                # pull from source into master.
1711
1581
                master_branch.pull(source, overwrite, stop_revision,
1712
 
                    run_hooks=False)
 
1582
                    _run_hooks=False)
1713
1583
            return super(BzrBranch5, self).pull(source, overwrite,
1714
1584
                stop_revision, _hook_master=master_branch,
1715
 
                run_hooks=run_hooks)
 
1585
                _run_hooks=_run_hooks)
 
1586
        finally:
 
1587
            if master_branch:
 
1588
                master_branch.unlock()
 
1589
 
 
1590
    @needs_read_lock
 
1591
    def push(self, target, overwrite=False, stop_revision=None):
 
1592
        """Updates branch.push to be bound branch aware."""
 
1593
        bound_location = target.get_bound_location()
 
1594
        master_branch = None
 
1595
        if bound_location and target.base != bound_location:
 
1596
            # not pushing to master, so we need to update master.
 
1597
            master_branch = target.get_master_branch()
 
1598
            master_branch.lock_write()
 
1599
        try:
 
1600
            if master_branch:
 
1601
                # push into the master from this branch.
 
1602
                super(BzrBranch5, self).push(master_branch, overwrite,
 
1603
                    stop_revision, _run_hooks=False)
 
1604
            # and push into the target branch from this. Note that we push from
 
1605
            # this branch again, because its considered the highest bandwidth
 
1606
            # repository.
 
1607
            return super(BzrBranch5, self).push(target, overwrite,
 
1608
                stop_revision, _hook_master=master_branch)
1716
1609
        finally:
1717
1610
            if master_branch:
1718
1611
                master_branch.unlock()
1782
1675
        # last_rev is not in the other_last_rev history, AND
1783
1676
        # other_last_rev is not in our history, and do it without pulling
1784
1677
        # history around
1785
 
        last_rev = _mod_revision.ensure_null(self.last_revision())
1786
 
        if last_rev != _mod_revision.NULL_REVISION:
 
1678
        last_rev = self.last_revision()
 
1679
        if last_rev is not None:
1787
1680
            other.lock_read()
1788
1681
            try:
1789
1682
                other_last_rev = other.last_revision()
1790
 
                if not _mod_revision.is_null(other_last_rev):
 
1683
                if other_last_rev is not None:
1791
1684
                    # neither branch is new, we have to do some work to
1792
1685
                    # ascertain diversion.
1793
1686
                    remote_graph = other.repository.get_revision_graph(
1816
1709
        if master is not None:
1817
1710
            old_tip = self.last_revision()
1818
1711
            self.pull(master, overwrite=True)
1819
 
            if old_tip in self.repository.get_ancestry(
1820
 
                _mod_revision.ensure_null(self.last_revision()),
1821
 
                topo_sorted=False):
 
1712
            if old_tip in self.repository.get_ancestry(self.last_revision()):
1822
1713
                return None
1823
1714
            return old_tip
1824
1715
        return None
1854
1745
        return "Experimental branch format"
1855
1746
 
1856
1747
    @classmethod
1857
 
    def get_reference(cls, a_bzrdir):
1858
 
        """Get the target reference of the branch in a_bzrdir.
1859
 
 
1860
 
        format probing must have been completed before calling
1861
 
        this method - it is assumed that the format of the branch
1862
 
        in a_bzrdir is correct.
1863
 
 
1864
 
        :param a_bzrdir: The bzrdir to get the branch data from.
1865
 
        :return: None if the branch is not a reference branch.
1866
 
        """
1867
 
        return None
1868
 
 
1869
 
    @classmethod
1870
1748
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1871
1749
            lock_class):
1872
1750
        branch_transport = a_bzrdir.get_branch_transport(cls)
1965
1843
        self._clear_cached_state()
1966
1844
 
1967
1845
    def _check_history_violation(self, revision_id):
1968
 
        last_revision = _mod_revision.ensure_null(self.last_revision())
1969
 
        if _mod_revision.is_null(last_revision):
 
1846
        last_revision = self.last_revision()
 
1847
        if last_revision is None:
1970
1848
            return
1971
1849
        if last_revision not in self._lefthand_history(revision_id):
1972
1850
            raise errors.AppendRevisionsOnlyViolation(self.base)
2036
1914
            if config.get_user_option('bound') != 'True':
2037
1915
                return False
2038
1916
            else:
2039
 
                config.set_user_option('bound', 'False', warn_masked=True)
 
1917
                config.set_user_option('bound', 'False')
2040
1918
                return True
2041
1919
        else:
2042
1920
            self._set_config_location('bound_location', location,
2043
1921
                                      config=config)
2044
 
            config.set_user_option('bound', 'True', warn_masked=True)
 
1922
            config.set_user_option('bound', 'True')
2045
1923
        return True
2046
1924
 
2047
1925
    def _get_bound_location(self, bound):
2067
1945
            value = 'True'
2068
1946
        else:
2069
1947
            value = 'False'
2070
 
        self.get_config().set_user_option('append_revisions_only', value,
2071
 
            warn_masked=True)
 
1948
        self.get_config().set_user_option('append_revisions_only', value)
2072
1949
 
2073
1950
    def _get_append_revisions_only(self):
2074
1951
        value = self.get_config().get_user_option('append_revisions_only')
2089
1966
        if revision_id is None:
2090
1967
            revno, revision_id = self.last_revision_info()
2091
1968
        else:
2092
 
            # To figure out the revno for a random revision, we need to build
2093
 
            # the revision history, and count its length.
2094
 
            # We don't care about the order, just how long it is.
2095
 
            # Alternatively, we could start at the current location, and count
2096
 
            # backwards. But there is no guarantee that we will find it since
2097
 
            # it may be a merged revision.
2098
 
            revno = len(list(self.repository.iter_reverse_revision_history(
2099
 
                                                                revision_id)))
 
1969
            revno = self.revision_id_to_revno(revision_id)
2100
1970
        destination.set_last_revision_info(revno, revision_id)
2101
1971
 
2102
1972
    def _make_tags(self):
2103
1973
        return BasicTags(self)
2104
1974
 
2105
1975
 
 
1976
class BranchTestProviderAdapter(object):
 
1977
    """A tool to generate a suite testing multiple branch formats at once.
 
1978
 
 
1979
    This is done by copying the test once for each transport and injecting
 
1980
    the transport_server, transport_readonly_server, and branch_format
 
1981
    classes into each copy. Each copy is also given a new id() to make it
 
1982
    easy to identify.
 
1983
    """
 
1984
 
 
1985
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1986
        self._transport_server = transport_server
 
1987
        self._transport_readonly_server = transport_readonly_server
 
1988
        self._formats = formats
 
1989
    
 
1990
    def adapt(self, test):
 
1991
        result = TestSuite()
 
1992
        for branch_format, bzrdir_format in self._formats:
 
1993
            new_test = deepcopy(test)
 
1994
            new_test.transport_server = self._transport_server
 
1995
            new_test.transport_readonly_server = self._transport_readonly_server
 
1996
            new_test.bzrdir_format = bzrdir_format
 
1997
            new_test.branch_format = branch_format
 
1998
            def make_new_test_id():
 
1999
                # the format can be either a class or an instance
 
2000
                name = getattr(branch_format, '__name__',
 
2001
                        branch_format.__class__.__name__)
 
2002
                new_id = "%s(%s)" % (new_test.id(), name)
 
2003
                return lambda: new_id
 
2004
            new_test.id = make_new_test_id()
 
2005
            result.addTest(new_test)
 
2006
        return result
 
2007
 
 
2008
 
2106
2009
######################################################################
2107
2010
# results of operations
2108
2011