~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
        )
41
41
from bzrlib.config import BranchConfig, TreeConfig
42
42
from bzrlib.lockable_files import LockableFiles, TransportLock
 
43
from bzrlib.tag import (
 
44
    BasicTags,
 
45
    DisabledTags,
 
46
    )
43
47
""")
44
48
 
45
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
88
92
    # - RBC 20060112
89
93
    base = None
90
94
 
 
95
    # override this to set the strategy for storing tags
 
96
    def _make_tags(self):
 
97
        return DisabledTags(self)
 
98
 
91
99
    def __init__(self, *ignored, **ignored_too):
92
 
        raise NotImplementedError('The Branch class is abstract')
 
100
        self.tags = self._make_tags()
93
101
 
94
102
    def break_lock(self):
95
103
        """Break a lock if one is present from another instance.
378
386
        """Given a revision id, return its revno"""
379
387
        if revision_id is None:
380
388
            return 0
 
389
        revision_id = osutils.safe_revision_id(revision_id)
381
390
        history = self.revision_history()
382
391
        try:
383
392
            return history.index(revision_id) + 1
398
407
        """Mirror source into this branch.
399
408
 
400
409
        This branch is considered to be 'local', having low latency.
 
410
 
 
411
        :returns: PullResult instance
401
412
        """
402
413
        raise NotImplementedError(self.pull)
403
414
 
580
591
        """
581
592
        new_history = self.revision_history()
582
593
        if revision_id is not None:
 
594
            revision_id = osutils.safe_revision_id(revision_id)
583
595
            try:
584
596
                new_history = new_history[:new_history.index(revision_id) + 1]
585
597
            except ValueError:
675
687
            checkout_branch.pull(self, stop_revision=revision_id)
676
688
        return checkout.create_workingtree(revision_id)
677
689
 
 
690
    def supports_tags(self):
 
691
        return self._format.supports_tags()
 
692
 
678
693
 
679
694
class BranchFormat(object):
680
695
    """An encapsulation of the initialization and open routines for a format.
794
809
    def __str__(self):
795
810
        return self.get_format_string().rstrip()
796
811
 
 
812
    def supports_tags(self):
 
813
        """True if this format supports tags stored in the branch"""
 
814
        return False  # by default
 
815
 
 
816
    # XXX: Probably doesn't really belong here -- mbp 20070212
 
817
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
818
            lock_class):
 
819
        branch_transport = a_bzrdir.get_branch_transport(self)
 
820
        control_files = lockable_files.LockableFiles(branch_transport,
 
821
            lock_filename, lock_class)
 
822
        control_files.create_lock()
 
823
        control_files.lock_write()
 
824
        try:
 
825
            for filename, content in utf8_files:
 
826
                control_files.put_utf8(filename, content)
 
827
        finally:
 
828
            control_files.unlock()
 
829
 
797
830
 
798
831
class BranchHooks(dict):
799
832
    """A dictionary mapping hook name to a list of callables for branch hooks.
817
850
        self['set_rh'] = []
818
851
        # invoked after a push operation completes.
819
852
        # the api signature is
 
853
        # (push_result)
 
854
        # containing the members
820
855
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
821
856
        # where local is the local branch or None, master is the target 
822
857
        # master branch, and the rest should be self explanatory. The source
825
860
        self['post_push'] = []
826
861
        # invoked after a pull operation completes.
827
862
        # the api signature is
 
863
        # (pull_result)
 
864
        # containing the members
828
865
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
829
866
        # where local is the local branch or None, master is the target 
830
867
        # master branch, and the rest should be self explanatory. The source
953
990
                          a_bzrdir=a_bzrdir,
954
991
                          _repository=a_bzrdir.find_repository())
955
992
 
956
 
    def __str__(self):
957
 
        return "Bazaar-NG Metadir branch format 5"
958
 
 
959
993
 
960
994
class BzrBranchFormat6(BzrBranchFormat5):
961
995
    """Branch format with last-revision
979
1013
        """Create a branch of this format in a_bzrdir."""
980
1014
        utf8_files = [('last-revision', '0 null:\n'),
981
1015
                      ('branch-name', ''),
982
 
                      ('branch.conf', '')
 
1016
                      ('branch.conf', ''),
 
1017
                      ('tags', ''),
983
1018
                      ]
984
1019
        return self._initialize_helper(a_bzrdir, utf8_files)
985
1020
 
1000
1035
                          a_bzrdir=a_bzrdir,
1001
1036
                          _repository=a_bzrdir.find_repository())
1002
1037
 
 
1038
    def supports_tags(self):
 
1039
        return True
 
1040
 
1003
1041
 
1004
1042
class BranchReferenceFormat(BranchFormat):
1005
1043
    """Bzr branch reference format.
1105
1143
            upgrade/recovery type use; it's not guaranteed that
1106
1144
            all operations will work on old format branches.
1107
1145
        """
 
1146
        Branch.__init__(self)
1108
1147
        if a_bzrdir is None:
1109
1148
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
1110
1149
        else:
1111
1150
            self.bzrdir = a_bzrdir
1112
 
        self._transport = self.bzrdir.transport.clone('..')
1113
 
        self._base = self._transport.base
 
1151
        # self._transport used to point to the directory containing the
 
1152
        # control directory, but was not used - now it's just the transport
 
1153
        # for the branch control files.  mbp 20070212
 
1154
        self._base = self.bzrdir.transport.clone('..').base
1114
1155
        self._format = _format
1115
1156
        if _control_files is None:
1116
1157
            raise ValueError('BzrBranch _control_files is None')
1117
1158
        self.control_files = _control_files
 
1159
        self._transport = _control_files._transport
1118
1160
        if deprecated_passed(init):
1119
1161
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1120
1162
                 "deprecated as of bzr 0.8. Please use Branch.create().",
1149
1191
    __repr__ = __str__
1150
1192
 
1151
1193
    def _get_base(self):
 
1194
        """Returns the directory containing the control directory."""
1152
1195
        return self._base
1153
1196
 
1154
1197
    base = property(_get_base, doc="The URL for the root of this branch.")
1245
1288
    @needs_write_lock
1246
1289
    def append_revision(self, *revision_ids):
1247
1290
        """See Branch.append_revision."""
 
1291
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1248
1292
        for revision_id in revision_ids:
1249
1293
            _mod_revision.check_not_reserved_id(revision_id)
1250
1294
            mutter("add {%s} to revision-history" % revision_id)
1257
1301
 
1258
1302
        This performs the actual writing to disk.
1259
1303
        It is intended to be called by BzrBranch5.set_revision_history."""
1260
 
        self.control_files.put_utf8(
 
1304
        self.control_files.put_bytes(
1261
1305
            'revision-history', '\n'.join(history))
1262
1306
 
1263
1307
    @needs_write_lock
1264
1308
    def set_revision_history(self, rev_history):
1265
1309
        """See Branch.set_revision_history."""
 
1310
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1266
1311
        self._write_revision_history(rev_history)
1267
1312
        transaction = self.get_transaction()
1268
1313
        history = transaction.map.find_revision_history()
1282
1327
 
1283
1328
    @needs_write_lock
1284
1329
    def set_last_revision_info(self, revno, revision_id):
 
1330
        revision_id = osutils.safe_revision_id(revision_id)
1285
1331
        history = self._lefthand_history(revision_id)
1286
1332
        assert len(history) == revno, '%d != %d' % (len(history), revno)
1287
1333
        self.set_revision_history(history)
1288
1334
 
1289
1335
    def _gen_revision_history(self):
1290
 
        decode_utf8 = cache_utf8.decode
1291
 
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1336
        get_cached_utf8 = cache_utf8.get_cached_utf8
 
1337
        history = [get_cached_utf8(l.rstrip('\r\n')) for l in
1292
1338
                self.control_files.get('revision-history').readlines()]
1293
1339
        return history
1294
1340
 
1338
1384
        :param other_branch: The other branch that DivergedBranches should
1339
1385
            raise with respect to.
1340
1386
        """
 
1387
        revision_id = osutils.safe_revision_id(revision_id)
1341
1388
        self.set_revision_history(self._lefthand_history(revision_id,
1342
1389
            last_rev, other_branch))
1343
1390
 
1351
1398
                if stop_revision is None:
1352
1399
                    # if there are no commits, we're done.
1353
1400
                    return
 
1401
            else:
 
1402
                stop_revision = osutils.safe_revision_id(stop_revision)
1354
1403
            # whats the current last revision, before we fetch [and change it
1355
1404
            # possibly]
1356
1405
            last_rev = self.last_revision()
1390
1439
        :param _run_hooks: Private parameter - allow disabling of
1391
1440
            hooks, used when pushing to a master branch.
1392
1441
        """
 
1442
        result = PullResult()
 
1443
        result.source_branch = source
 
1444
        result.target_branch = self
1393
1445
        source.lock_read()
1394
1446
        try:
1395
 
            old_count, old_tip = self.last_revision_info()
 
1447
            result.old_revno, result.old_revid = self.last_revision_info()
1396
1448
            try:
1397
1449
                self.update_revisions(source, stop_revision)
1398
1450
            except DivergedBranches:
1399
1451
                if not overwrite:
1400
1452
                    raise
1401
1453
            if overwrite:
1402
 
                self.set_revision_history(source.revision_history())
1403
 
            new_count, new_tip = self.last_revision_info()
 
1454
                if stop_revision is None:
 
1455
                    stop_revision = source.last_revision()
 
1456
                self.generate_revision_history(stop_revision)
 
1457
            result.tag_conflicts = source.tags.merge_to(self.tags)
 
1458
            result.new_revno, result.new_revid = self.last_revision_info()
 
1459
            if _hook_master:
 
1460
                result.master_branch = _hook_master
 
1461
                result.local_branch = self
 
1462
            else:
 
1463
                result.master_branch = self
 
1464
                result.local_branch = None
1404
1465
            if _run_hooks:
1405
 
                if _hook_master:
1406
 
                    _hook_local = self
1407
 
                else:
1408
 
                    _hook_master = self
1409
 
                    _hook_local = None
1410
1466
                for hook in Branch.hooks['post_pull']:
1411
 
                    hook(source, _hook_local, _hook_master, old_count, old_tip,
1412
 
                        new_count, new_tip)
1413
 
            return new_count - old_count
 
1467
                    hook(result)
1414
1468
        finally:
1415
1469
            source.unlock()
 
1470
        return result
1416
1471
 
1417
1472
    def _get_parent_location(self):
1418
1473
        _locs = ['parent', 'pull', 'x-pull']
1433
1488
        :param _run_hooks: Private parameter - allow disabling of
1434
1489
            hooks, used when pushing to a master branch.
1435
1490
        """
 
1491
        result = PushResult()
 
1492
        result.source_branch = self
 
1493
        result.target_branch = target
1436
1494
        target.lock_write()
1437
1495
        try:
1438
 
            old_count, old_tip = target.last_revision_info()
 
1496
            result.old_revno, result.old_revid = target.last_revision_info()
1439
1497
            try:
1440
1498
                target.update_revisions(self, stop_revision)
1441
1499
            except DivergedBranches:
1443
1501
                    raise
1444
1502
            if overwrite:
1445
1503
                target.set_revision_history(self.revision_history())
1446
 
            new_count, new_tip = target.last_revision_info()
 
1504
            result.tag_conflicts = self.tags.merge_to(target.tags)
 
1505
            result.new_revno, result.new_revid = target.last_revision_info()
 
1506
            if _hook_master:
 
1507
                result.master_branch = _hook_master
 
1508
                result.local_branch = target
 
1509
            else:
 
1510
                result.master_branch = target
 
1511
                result.local_branch = None
1447
1512
            if _run_hooks:
1448
 
                if _hook_master:
1449
 
                    _hook_local = target
1450
 
                else:
1451
 
                    _hook_master = target
1452
 
                    _hook_local = None
1453
1513
                for hook in Branch.hooks['post_push']:
1454
 
                    hook(self, _hook_local, _hook_master, old_count, old_tip,
1455
 
                        new_count, new_tip)
1456
 
            return new_count - old_count
 
1514
                    hook(result)
1457
1515
        finally:
1458
1516
            target.unlock()
 
1517
        return result
1459
1518
 
1460
1519
    def get_parent(self):
1461
1520
        """See Branch.get_parent."""
1500
1559
                    raise bzrlib.errors.InvalidURL(url,
1501
1560
                        "Urls must be 7-bit ascii, "
1502
1561
                        "use bzrlib.urlutils.escape")
1503
 
                    
1504
1562
            url = urlutils.relative_url(self.base, url)
1505
1563
        self._set_parent_location(url)
1506
1564
 
1509
1567
            self.control_files._transport.delete('parent')
1510
1568
        else:
1511
1569
            assert isinstance(url, str)
1512
 
            self.control_files.put('parent', StringIO(url + '\n'))
 
1570
            self.control_files.put_bytes('parent', url + '\n')
1513
1571
 
1514
1572
    @deprecated_function(zero_nine)
1515
1573
    def tree_config(self):
1688
1746
        return None
1689
1747
 
1690
1748
 
 
1749
class BzrBranchExperimental(BzrBranch5):
 
1750
    """Bzr experimental branch format
 
1751
 
 
1752
    This format has:
 
1753
     - a revision-history file.
 
1754
     - a format string
 
1755
     - a lock dir guarding the branch itself
 
1756
     - all of this stored in a branch/ subdirectory
 
1757
     - works with shared repositories.
 
1758
     - a tag dictionary in the branch
 
1759
 
 
1760
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
1761
    only for testing.
 
1762
 
 
1763
    This class acts as it's own BranchFormat.
 
1764
    """
 
1765
 
 
1766
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1767
 
 
1768
    @classmethod
 
1769
    def get_format_string(cls):
 
1770
        """See BranchFormat.get_format_string()."""
 
1771
        return "Bazaar-NG branch format experimental\n"
 
1772
 
 
1773
    @classmethod
 
1774
    def get_format_description(cls):
 
1775
        """See BranchFormat.get_format_description()."""
 
1776
        return "Experimental branch format"
 
1777
 
 
1778
    @classmethod
 
1779
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
 
1780
            lock_class):
 
1781
        branch_transport = a_bzrdir.get_branch_transport(cls)
 
1782
        control_files = lockable_files.LockableFiles(branch_transport,
 
1783
            lock_filename, lock_class)
 
1784
        control_files.create_lock()
 
1785
        control_files.lock_write()
 
1786
        try:
 
1787
            for filename, content in utf8_files:
 
1788
                control_files.put_utf8(filename, content)
 
1789
        finally:
 
1790
            control_files.unlock()
 
1791
        
 
1792
    @classmethod
 
1793
    def initialize(cls, a_bzrdir):
 
1794
        """Create a branch of this format in a_bzrdir."""
 
1795
        utf8_files = [('format', cls.get_format_string()),
 
1796
                      ('revision-history', ''),
 
1797
                      ('branch-name', ''),
 
1798
                      ('tags', ''),
 
1799
                      ]
 
1800
        cls._initialize_control_files(a_bzrdir, utf8_files,
 
1801
            'lock', lockdir.LockDir)
 
1802
        return cls.open(a_bzrdir, _found=True)
 
1803
 
 
1804
    @classmethod
 
1805
    def open(cls, a_bzrdir, _found=False):
 
1806
        """Return the branch object for a_bzrdir
 
1807
 
 
1808
        _found is a private parameter, do not use it. It is used to indicate
 
1809
               if format probing has already be done.
 
1810
        """
 
1811
        if not _found:
 
1812
            format = BranchFormat.find_format(a_bzrdir)
 
1813
            assert format.__class__ == cls
 
1814
        transport = a_bzrdir.get_branch_transport(None)
 
1815
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1816
                                                     lockdir.LockDir)
 
1817
        return cls(_format=cls,
 
1818
            _control_files=control_files,
 
1819
            a_bzrdir=a_bzrdir,
 
1820
            _repository=a_bzrdir.find_repository())
 
1821
 
 
1822
    @classmethod
 
1823
    def is_supported(cls):
 
1824
        return True
 
1825
 
 
1826
    def _make_tags(self):
 
1827
        return BasicTags(self)
 
1828
 
 
1829
    @classmethod
 
1830
    def supports_tags(cls):
 
1831
        return True
 
1832
 
 
1833
 
 
1834
BranchFormat.register_format(BzrBranchExperimental)
 
1835
 
 
1836
 
1691
1837
class BzrBranch6(BzrBranch5):
1692
1838
 
1693
1839
    @needs_read_lock
1694
1840
    def last_revision_info(self):
1695
 
        revision_string = self.control_files.get_utf8('last-revision').read()
 
1841
        revision_string = self.control_files.get('last-revision').read()
1696
1842
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
 
1843
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1697
1844
        revno = int(revno)
1698
1845
        return revno, revision_id
1699
1846
 
1716
1863
        if revision_id is None:
1717
1864
            revision_id = 'null:'
1718
1865
        out_string = '%d %s\n' % (revno, revision_id)
1719
 
        self.control_files.put_utf8('last-revision', out_string)
 
1866
        self.control_files.put_bytes('last-revision', out_string)
1720
1867
 
1721
1868
    @needs_write_lock
1722
1869
    def set_last_revision_info(self, revno, revision_id):
 
1870
        revision_id = osutils.safe_revision_id(revision_id)
1723
1871
        if self._get_append_revisions_only():
1724
1872
            self._check_history_violation(revision_id)
1725
1873
        self._write_last_revision_info(revno, revision_id)
1761
1909
 
1762
1910
    @needs_write_lock
1763
1911
    def append_revision(self, *revision_ids):
 
1912
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1764
1913
        if len(revision_ids) == 0:
1765
1914
            return
1766
1915
        prev_revno, prev_revision = self.last_revision_info()
1873
2022
            revno = self.revision_id_to_revno(revision_id)
1874
2023
        destination.set_last_revision_info(revno, revision_id)
1875
2024
 
 
2025
    def _make_tags(self):
 
2026
        return BasicTags(self)
 
2027
 
1876
2028
 
1877
2029
class BranchTestProviderAdapter(object):
1878
2030
    """A tool to generate a suite testing multiple branch formats at once.
1897
2049
            new_test.bzrdir_format = bzrdir_format
1898
2050
            new_test.branch_format = branch_format
1899
2051
            def make_new_test_id():
1900
 
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
2052
                # the format can be either a class or an instance
 
2053
                name = getattr(branch_format, '__name__',
 
2054
                        branch_format.__class__.__name__)
 
2055
                new_id = "%s(%s)" % (new_test.id(), name)
1901
2056
                return lambda: new_id
1902
2057
            new_test.id = make_new_test_id()
1903
2058
            result.addTest(new_test)
1904
2059
        return result
1905
2060
 
1906
2061
 
 
2062
######################################################################
 
2063
# results of operations
 
2064
 
 
2065
 
 
2066
class _Result(object):
 
2067
 
 
2068
    def _show_tag_conficts(self, to_file):
 
2069
        if not getattr(self, 'tag_conflicts', None):
 
2070
            return
 
2071
        to_file.write('Conflicting tags:\n')
 
2072
        for name, value1, value2 in self.tag_conflicts:
 
2073
            to_file.write('    %s\n' % (name, ))
 
2074
 
 
2075
 
 
2076
class PullResult(_Result):
 
2077
    """Result of a Branch.pull operation.
 
2078
 
 
2079
    :ivar old_revno: Revision number before pull.
 
2080
    :ivar new_revno: Revision number after pull.
 
2081
    :ivar old_revid: Tip revision id before pull.
 
2082
    :ivar new_revid: Tip revision id after pull.
 
2083
    :ivar source_branch: Source (local) branch object.
 
2084
    :ivar master_branch: Master branch of the target, or None.
 
2085
    :ivar target_branch: Target/destination branch object.
 
2086
    """
 
2087
 
 
2088
    def __int__(self):
 
2089
        # DEPRECATED: pull used to return the change in revno
 
2090
        return self.new_revno - self.old_revno
 
2091
 
 
2092
    def report(self, to_file):
 
2093
        if self.old_revid == self.new_revid:
 
2094
            to_file.write('No revisions to pull.\n')
 
2095
        else:
 
2096
            to_file.write('Now on revision %d.\n' % self.new_revno)
 
2097
        self._show_tag_conficts(to_file)
 
2098
 
 
2099
 
 
2100
class PushResult(_Result):
 
2101
    """Result of a Branch.push operation.
 
2102
 
 
2103
    :ivar old_revno: Revision number before push.
 
2104
    :ivar new_revno: Revision number after push.
 
2105
    :ivar old_revid: Tip revision id before push.
 
2106
    :ivar new_revid: Tip revision id after push.
 
2107
    :ivar source_branch: Source branch object.
 
2108
    :ivar master_branch: Master branch of the target, or None.
 
2109
    :ivar target_branch: Target/destination branch object.
 
2110
    """
 
2111
 
 
2112
    def __int__(self):
 
2113
        # DEPRECATED: push used to return the change in revno
 
2114
        return self.new_revno - self.old_revno
 
2115
 
 
2116
    def report(self, to_file):
 
2117
        """Write a human-readable description of the result."""
 
2118
        if self.old_revid == self.new_revid:
 
2119
            to_file.write('No new revisions to push.\n')
 
2120
        else:
 
2121
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
 
2122
        self._show_tag_conficts(to_file)
 
2123
 
 
2124
 
1907
2125
class BranchCheckResult(object):
1908
2126
    """Results of checking branch consistency.
1909
2127
 
1924
2142
             self.branch._format)
1925
2143
 
1926
2144
 
1927
 
######################################################################
1928
 
# predicates
1929
 
 
1930
 
 
1931
 
@deprecated_function(zero_eight)
1932
 
def is_control_file(*args, **kwargs):
1933
 
    """See bzrlib.workingtree.is_control_file."""
1934
 
    from bzrlib import workingtree
1935
 
    return workingtree.is_control_file(*args, **kwargs)
1936
 
 
1937
 
 
1938
2145
class Converter5to6(object):
1939
2146
    """Perform an in-place upgrade of format 5 to format 6"""
1940
2147
 
1949
2156
        new_branch.set_bound_location(branch.get_bound_location())
1950
2157
        new_branch.set_push_location(branch.get_push_location())
1951
2158
 
 
2159
        # New branch has no tags by default
 
2160
        new_branch.tags._set_tag_dict({})
 
2161
 
1952
2162
        # Copying done; now update target format
1953
2163
        new_branch.control_files.put_utf8('format',
1954
2164
            format.get_format_string())