~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Andrew Bennetts
  • Date: 2008-07-28 06:53:44 UTC
  • mfrom: (3581 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3583.
  • Revision ID: andrew.bennetts@canonical.com-20080728065344-ocndjoycs903q6fz
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
import sys
 
19
 
18
20
from bzrlib.lazy_import import lazy_import
19
21
lazy_import(globals(), """
 
22
from itertools import chain
20
23
from bzrlib import (
21
24
        bzrdir,
22
25
        cache_utf8,
33
36
        urlutils,
34
37
        )
35
38
from bzrlib.config import BranchConfig
 
39
from bzrlib.repofmt.pack_repo import RepositoryFormatPackDevelopment1Subtree
36
40
from bzrlib.tag import (
37
41
    BasicTags,
38
42
    DisabledTags,
86
90
        self._revision_history_cache = None
87
91
        self._revision_id_to_revno_cache = None
88
92
        self._last_revision_info_cache = None
 
93
        self._open_hook()
 
94
 
 
95
    def _open_hook(self):
 
96
        """Called by init to allow simpler extension of the base class."""
89
97
 
90
98
    def break_lock(self):
91
99
        """Break a lock if one is present from another instance.
325
333
            raise errors.InvalidRevisionNumber(revno)
326
334
        return self.repository.get_revision_delta(rh[revno-1])
327
335
 
 
336
    def get_stacked_on_url(self):
 
337
        """Get the URL this branch is stacked against.
 
338
 
 
339
        :raises NotStacked: If the branch is not stacked.
 
340
        :raises UnstackableBranchFormat: If the branch does not support
 
341
            stacking.
 
342
        """
 
343
        raise NotImplementedError(self.get_stacked_on_url)
 
344
 
328
345
    def print_file(self, file, revision_id):
329
346
        """Print `file` to stdout."""
330
347
        raise NotImplementedError(self.print_file)
332
349
    def set_revision_history(self, rev_history):
333
350
        raise NotImplementedError(self.set_revision_history)
334
351
 
 
352
    def set_stacked_on_url(self, url):
 
353
        """Set the URL this branch is stacked against.
 
354
 
 
355
        :raises UnstackableBranchFormat: If the branch does not support
 
356
            stacking.
 
357
        :raises UnstackableRepositoryFormat: If the repository does not support
 
358
            stacking.
 
359
        """
 
360
        raise NotImplementedError(self.set_stacked_on_url)
 
361
 
335
362
    def _cache_revision_history(self, rev_history):
336
363
        """Set the cached revision history to rev_history.
337
364
 
508
535
        finally:
509
536
            other.unlock()
510
537
 
511
 
 
512
 
 
513
538
    def revision_id_to_revno(self, revision_id):
514
539
        """Given a revision id, return its revno"""
515
540
        if _mod_revision.is_null(revision_id):
874
899
        elif relation == 'a_descends_from_b':
875
900
            return False
876
901
        else:
877
 
            raise AssertionError("invalid heads: %r" % heads)
 
902
            raise AssertionError("invalid relation: %r" % (relation,))
878
903
 
879
904
    def _revision_relations(self, revision_a, revision_b, graph):
880
905
        """Determine the relationship between two revisions.
890
915
        elif heads == set([revision_a]):
891
916
            return 'a_descends_from_b'
892
917
        else:
893
 
            raise AssertionError("invalid heads: %r" % heads)
 
918
            raise AssertionError("invalid heads: %r" % (heads,))
894
919
 
895
920
 
896
921
class BranchFormat(object):
1036
1061
    def set_default_format(klass, format):
1037
1062
        klass._default_format = format
1038
1063
 
 
1064
    def supports_stacking(self):
 
1065
        """True if this format records a stacked-on branch."""
 
1066
        return False
 
1067
 
1039
1068
    @classmethod
1040
1069
    def unregister_format(klass, format):
1041
1070
        del klass._formats[format.get_format_string()]
1109
1138
        # local is the local branch or None, master is the target branch,
1110
1139
        # and an empty branch recieves new_revno of 0, new_revid of None.
1111
1140
        self['post_uncommit'] = []
 
1141
        # Introduced in 1.6
 
1142
        # Invoked before the tip of a branch changes.
 
1143
        # the api signature is
 
1144
        # (params) where params is a ChangeBranchTipParams with the members
 
1145
        # (branch, old_revno, new_revno, old_revid, new_revid)
 
1146
        self['pre_change_branch_tip'] = []
1112
1147
        # Introduced in 1.4
1113
1148
        # Invoked after the tip of a branch changes.
1114
1149
        # the api signature is
1150
1185
        self.old_revid = old_revid
1151
1186
        self.new_revid = new_revid
1152
1187
 
 
1188
    def __eq__(self, other):
 
1189
        return self.__dict__ == other.__dict__
 
1190
    
 
1191
    def __repr__(self):
 
1192
        return "<%s of %s from (%s, %s) to (%s, %s)>" % (
 
1193
            self.__class__.__name__, self.branch, 
 
1194
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
 
1195
 
1153
1196
 
1154
1197
class BzrBranchFormat4(BranchFormat):
1155
1198
    """Bzr branch format 4.
1193
1236
        return "Bazaar-NG branch format 4"
1194
1237
 
1195
1238
 
1196
 
class BzrBranchFormat5(BranchFormat):
 
1239
class BranchFormatMetadir(BranchFormat):
 
1240
    """Common logic for meta-dir based branch formats."""
 
1241
 
 
1242
    def _branch_class(self):
 
1243
        """What class to instantiate on open calls."""
 
1244
        raise NotImplementedError(self._branch_class)
 
1245
 
 
1246
    def open(self, a_bzrdir, _found=False):
 
1247
        """Return the branch object for a_bzrdir.
 
1248
 
 
1249
        _found is a private parameter, do not use it. It is used to indicate
 
1250
               if format probing has already be done.
 
1251
        """
 
1252
        if not _found:
 
1253
            format = BranchFormat.find_format(a_bzrdir)
 
1254
            if format.__class__ != self.__class__:
 
1255
                raise AssertionError("wrong format %r found for %r" %
 
1256
                    (format, self))
 
1257
        try:
 
1258
            transport = a_bzrdir.get_branch_transport(None)
 
1259
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
1260
                                                         lockdir.LockDir)
 
1261
            return self._branch_class()(_format=self,
 
1262
                              _control_files=control_files,
 
1263
                              a_bzrdir=a_bzrdir,
 
1264
                              _repository=a_bzrdir.find_repository())
 
1265
        except errors.NoSuchFile:
 
1266
            raise errors.NotBranchError(path=transport.base)
 
1267
 
 
1268
    def __init__(self):
 
1269
        super(BranchFormatMetadir, self).__init__()
 
1270
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1271
 
 
1272
    def supports_tags(self):
 
1273
        return True
 
1274
 
 
1275
 
 
1276
class BzrBranchFormat5(BranchFormatMetadir):
1197
1277
    """Bzr branch format 5.
1198
1278
 
1199
1279
    This format has:
1206
1286
    This format is new in bzr 0.8.
1207
1287
    """
1208
1288
 
 
1289
    def _branch_class(self):
 
1290
        return BzrBranch5
 
1291
 
1209
1292
    def get_format_string(self):
1210
1293
        """See BranchFormat.get_format_string()."""
1211
1294
        return "Bazaar-NG branch format 5\n"
1221
1304
                      ]
1222
1305
        return self._initialize_helper(a_bzrdir, utf8_files)
1223
1306
 
1224
 
    def __init__(self):
1225
 
        super(BzrBranchFormat5, self).__init__()
1226
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1227
 
 
1228
 
    def open(self, a_bzrdir, _found=False):
1229
 
        """Return the branch object for a_bzrdir
1230
 
 
1231
 
        _found is a private parameter, do not use it. It is used to indicate
1232
 
               if format probing has already be done.
1233
 
        """
1234
 
        if not _found:
1235
 
            format = BranchFormat.find_format(a_bzrdir)
1236
 
            if format.__class__ != self.__class__:
1237
 
                raise AssertionError("wrong format %r found for %r" %
1238
 
                    (format, self))
1239
 
        try:
1240
 
            transport = a_bzrdir.get_branch_transport(None)
1241
 
            control_files = lockable_files.LockableFiles(transport, 'lock',
1242
 
                                                         lockdir.LockDir)
1243
 
            return BzrBranch5(_format=self,
1244
 
                              _control_files=control_files,
1245
 
                              a_bzrdir=a_bzrdir,
1246
 
                              _repository=a_bzrdir.find_repository())
1247
 
        except errors.NoSuchFile:
1248
 
            raise errors.NotBranchError(path=transport.base)
1249
 
 
1250
 
 
1251
 
class BzrBranchFormat6(BzrBranchFormat5):
 
1307
    def supports_tags(self):
 
1308
        return False
 
1309
 
 
1310
 
 
1311
class BzrBranchFormat6(BranchFormatMetadir):
1252
1312
    """Branch format with last-revision and tags.
1253
1313
 
1254
1314
    Unlike previous formats, this has no explicit revision history. Instead,
1259
1319
    and became the default in 0.91.
1260
1320
    """
1261
1321
 
 
1322
    def _branch_class(self):
 
1323
        return BzrBranch6
 
1324
 
1262
1325
    def get_format_string(self):
1263
1326
        """See BranchFormat.get_format_string()."""
1264
1327
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1275
1338
                      ]
1276
1339
        return self._initialize_helper(a_bzrdir, utf8_files)
1277
1340
 
1278
 
    def open(self, a_bzrdir, _found=False):
1279
 
        """Return the branch object for a_bzrdir
1280
 
 
1281
 
        _found is a private parameter, do not use it. It is used to indicate
1282
 
               if format probing has already be done.
1283
 
        """
1284
 
        if not _found:
1285
 
            format = BranchFormat.find_format(a_bzrdir)
1286
 
            if format.__class__ != self.__class__:
1287
 
                raise AssertionError("wrong format %r found for %r" %
1288
 
                    (format, self))
1289
 
        transport = a_bzrdir.get_branch_transport(None)
1290
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1291
 
                                                     lockdir.LockDir)
1292
 
        return BzrBranch6(_format=self,
1293
 
                          _control_files=control_files,
1294
 
                          a_bzrdir=a_bzrdir,
1295
 
                          _repository=a_bzrdir.find_repository())
1296
 
 
1297
 
    def supports_tags(self):
 
1341
 
 
1342
class BzrBranchFormat7(BranchFormatMetadir):
 
1343
    """Branch format with last-revision, tags, and a stacked location pointer.
 
1344
 
 
1345
    The stacked location pointer is passed down to the repository and requires
 
1346
    a repository format with supports_external_lookups = True.
 
1347
 
 
1348
    This format was introduced in bzr 1.6.
 
1349
    """
 
1350
 
 
1351
    def _branch_class(self):
 
1352
        return BzrBranch7
 
1353
 
 
1354
    def get_format_string(self):
 
1355
        """See BranchFormat.get_format_string()."""
 
1356
        return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
 
1357
 
 
1358
    def get_format_description(self):
 
1359
        """See BranchFormat.get_format_description()."""
 
1360
        return "Branch format 7"
 
1361
 
 
1362
    def initialize(self, a_bzrdir):
 
1363
        """Create a branch of this format in a_bzrdir."""
 
1364
        utf8_files = [('last-revision', '0 null:\n'),
 
1365
                      ('branch.conf', ''),
 
1366
                      ('tags', ''),
 
1367
                      ]
 
1368
        return self._initialize_helper(a_bzrdir, utf8_files)
 
1369
 
 
1370
    def __init__(self):
 
1371
        super(BzrBranchFormat7, self).__init__()
 
1372
        self._matchingbzrdir.repository_format = \
 
1373
            RepositoryFormatPackDevelopment1Subtree()
 
1374
 
 
1375
    def supports_stacking(self):
1298
1376
        return True
1299
1377
 
1300
1378
 
1389
1467
# and not independently creatable, so are not registered.
1390
1468
__format5 = BzrBranchFormat5()
1391
1469
__format6 = BzrBranchFormat6()
 
1470
__format7 = BzrBranchFormat7()
1392
1471
BranchFormat.register_format(__format5)
1393
1472
BranchFormat.register_format(BranchReferenceFormat())
1394
1473
BranchFormat.register_format(__format6)
 
1474
BranchFormat.register_format(__format7)
1395
1475
BranchFormat.set_default_format(__format6)
1396
1476
_legacy_formats = [BzrBranchFormat4(),
1397
1477
                   ]
1413
1493
    def __init__(self, _format=None,
1414
1494
                 _control_files=None, a_bzrdir=None, _repository=None):
1415
1495
        """Create new branch object at a particular location."""
1416
 
        Branch.__init__(self)
1417
1496
        if a_bzrdir is None:
1418
1497
            raise ValueError('a_bzrdir must be supplied')
1419
1498
        else:
1428
1507
        self.control_files = _control_files
1429
1508
        self._transport = _control_files._transport
1430
1509
        self.repository = _repository
 
1510
        Branch.__init__(self)
1431
1511
 
1432
1512
    def __str__(self):
1433
1513
        return '%s(%r)' % (self.__class__.__name__, self.base)
1506
1586
        check_not_reserved_id = _mod_revision.check_not_reserved_id
1507
1587
        for rev_id in rev_history:
1508
1588
            check_not_reserved_id(rev_id)
 
1589
        if Branch.hooks['post_change_branch_tip']:
 
1590
            # Don't calculate the last_revision_info() if there are no hooks
 
1591
            # that will use it.
 
1592
            old_revno, old_revid = self.last_revision_info()
 
1593
        if len(rev_history) == 0:
 
1594
            revid = _mod_revision.NULL_REVISION
 
1595
        else:
 
1596
            revid = rev_history[-1]
 
1597
        self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
1509
1598
        self._write_revision_history(rev_history)
1510
1599
        self._clear_cached_state()
1511
1600
        self._cache_revision_history(rev_history)
1512
1601
        for hook in Branch.hooks['set_rh']:
1513
1602
            hook(self, rev_history)
 
1603
        if Branch.hooks['post_change_branch_tip']:
 
1604
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1514
1605
 
 
1606
    def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
 
1607
        """Run the pre_change_branch_tip hooks."""
 
1608
        hooks = Branch.hooks['pre_change_branch_tip']
 
1609
        if not hooks:
 
1610
            return
 
1611
        old_revno, old_revid = self.last_revision_info()
 
1612
        params = ChangeBranchTipParams(
 
1613
            self, old_revno, new_revno, old_revid, new_revid)
 
1614
        for hook in hooks:
 
1615
            try:
 
1616
                hook(params)
 
1617
            except errors.TipChangeRejected:
 
1618
                raise
 
1619
            except Exception:
 
1620
                exc_info = sys.exc_info()
 
1621
                hook_name = Branch.hooks.get_hook_name(hook)
 
1622
                raise errors.HookFailed(
 
1623
                    'pre_change_branch_tip', hook_name, exc_info)
 
1624
 
1515
1625
    def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1516
1626
        """Run the post_change_branch_tip hooks."""
1517
1627
        hooks = Branch.hooks['post_change_branch_tip']
1536
1646
        be permitted.
1537
1647
        """
1538
1648
        revision_id = _mod_revision.ensure_null(revision_id)
1539
 
        old_revno, old_revid = self.last_revision_info()
1540
1649
        # this old format stores the full history, but this api doesn't
1541
1650
        # provide it, so we must generate, and might as well check it's
1542
1651
        # correct
1544
1653
        if len(history) != revno:
1545
1654
            raise AssertionError('%d != %d' % (len(history), revno))
1546
1655
        self.set_revision_history(history)
1547
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1548
1656
 
1549
1657
    def _gen_revision_history(self):
1550
1658
        history = self._transport.get_bytes('revision-history').split('\n')
1752
1860
        except errors.InvalidURLJoin, e:
1753
1861
            raise errors.InaccessibleParent(parent, self.base)
1754
1862
 
 
1863
    def get_stacked_on_url(self):
 
1864
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
1865
 
1755
1866
    def set_push_location(self, location):
1756
1867
        """See Branch.set_push_location."""
1757
1868
        self.get_config().set_user_option(
1783
1894
            self._transport.put_bytes('parent', url + '\n',
1784
1895
                mode=self.bzrdir._get_file_mode())
1785
1896
 
 
1897
    def set_stacked_on_url(self, url):
 
1898
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
1899
 
1786
1900
 
1787
1901
class BzrBranch5(BzrBranch):
1788
1902
    """A format 5 branch. This supports new features over plain branches.
1790
1904
    It has support for a master_branch which is the data for bound branches.
1791
1905
    """
1792
1906
 
1793
 
    def __init__(self,
1794
 
                 _format,
1795
 
                 _control_files,
1796
 
                 a_bzrdir,
1797
 
                 _repository):
1798
 
        super(BzrBranch5, self).__init__(_format=_format,
1799
 
                                         _control_files=_control_files,
1800
 
                                         a_bzrdir=a_bzrdir,
1801
 
                                         _repository=_repository)
1802
 
        
1803
1907
    @needs_write_lock
1804
1908
    def pull(self, source, overwrite=False, stop_revision=None,
1805
1909
             run_hooks=True, possible_transports=None,
1921
2025
        return None
1922
2026
 
1923
2027
 
1924
 
class BzrBranch6(BzrBranch5):
 
2028
class BzrBranch7(BzrBranch5):
 
2029
    """A branch with support for a fallback repository."""
 
2030
 
 
2031
    def _get_fallback_repository(self, url):
 
2032
        """Get the repository we fallback to at url."""
 
2033
        url = urlutils.join(self.base, url)
 
2034
        return bzrdir.BzrDir.open(url).open_branch().repository
 
2035
 
 
2036
    def _activate_fallback_location(self, url):
 
2037
        """Activate the branch/repository from url as a fallback repository."""
 
2038
        self.repository.add_fallback_repository(
 
2039
            self._get_fallback_repository(url))
 
2040
 
 
2041
    def _open_hook(self):
 
2042
        try:
 
2043
            url = self.get_stacked_on_url()
 
2044
        except (errors.UnstackableRepositoryFormat, errors.NotStacked,
 
2045
            errors.UnstackableBranchFormat):
 
2046
            pass
 
2047
        else:
 
2048
            self._activate_fallback_location(url)
 
2049
 
 
2050
    def _check_stackable_repo(self):
 
2051
        if not self.repository._format.supports_external_lookups:
 
2052
            raise errors.UnstackableRepositoryFormat(self.repository._format,
 
2053
                self.repository.base)
1925
2054
 
1926
2055
    def __init__(self, *args, **kwargs):
1927
 
        super(BzrBranch6, self).__init__(*args, **kwargs)
 
2056
        super(BzrBranch7, self).__init__(*args, **kwargs)
 
2057
        self._last_revision_info_cache = None
1928
2058
        self._partial_revision_history_cache = []
1929
2059
 
1930
2060
    def _clear_cached_state(self):
1931
 
        super(BzrBranch6, self)._clear_cached_state()
 
2061
        super(BzrBranch7, self)._clear_cached_state()
 
2062
        self._last_revision_info_cache = None
1932
2063
        self._partial_revision_history_cache = []
1933
2064
 
1934
2065
    def _last_revision_info(self):
1958
2089
        old_revno, old_revid = self.last_revision_info()
1959
2090
        if self._get_append_revisions_only():
1960
2091
            self._check_history_violation(revision_id)
 
2092
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
1961
2093
        self._write_last_revision_info(revno, revision_id)
1962
2094
        self._clear_cached_state()
1963
2095
        self._last_revision_info_cache = revno, revision_id
2070
2202
        """See Branch.get_old_bound_location"""
2071
2203
        return self._get_bound_location(False)
2072
2204
 
 
2205
    def get_stacked_on_url(self):
 
2206
        self._check_stackable_repo()
 
2207
        stacked_url = self._get_config_location('stacked_on_location')
 
2208
        if stacked_url is None:
 
2209
            raise errors.NotStacked(self)
 
2210
        return stacked_url
 
2211
 
2073
2212
    def set_append_revisions_only(self, enabled):
2074
2213
        if enabled:
2075
2214
            value = 'True'
2078
2217
        self.get_config().set_user_option('append_revisions_only', value,
2079
2218
            warn_masked=True)
2080
2219
 
 
2220
    def set_stacked_on_url(self, url):
 
2221
        self._check_stackable_repo()
 
2222
        if not url:
 
2223
            try:
 
2224
                old_url = self.get_stacked_on_url()
 
2225
            except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2226
                errors.UnstackableRepositoryFormat):
 
2227
                return
 
2228
            url = ''
 
2229
            # repositories don't offer an interface to remove fallback
 
2230
            # repositories today; take the conceptually simpler option and just
 
2231
            # reopen it.
 
2232
            self.repository = self.bzrdir.find_repository()
 
2233
            # for every revision reference the branch has, ensure it is pulled
 
2234
            # in.
 
2235
            source_repository = self._get_fallback_repository(old_url)
 
2236
            for revision_id in chain([self.last_revision()],
 
2237
                self.tags.get_reverse_tag_dict()):
 
2238
                self.repository.fetch(source_repository, revision_id,
 
2239
                    find_ghosts=True)
 
2240
        else:
 
2241
            self._activate_fallback_location(url)
 
2242
        # write this out after the repository is stacked to avoid setting a
 
2243
        # stacked config that doesn't work.
 
2244
        self._set_config_location('stacked_on_location', url)
 
2245
 
2081
2246
    def _get_append_revisions_only(self):
2082
2247
        value = self.get_config().get_user_option('append_revisions_only')
2083
2248
        return value == 'True'
2158
2323
        return self.revno() - index
2159
2324
 
2160
2325
 
 
2326
class BzrBranch6(BzrBranch7):
 
2327
    """See BzrBranchFormat6 for the capabilities of this branch.
 
2328
 
 
2329
    This subclass of BzrBranch7 disables the new features BzrBranch7 added,
 
2330
    i.e. stacking.
 
2331
    """
 
2332
 
 
2333
    def get_stacked_on_url(self):
 
2334
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2335
 
 
2336
    def set_stacked_on_url(self, url):
 
2337
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2338
 
 
2339
 
2161
2340
######################################################################
2162
2341
# results of operations
2163
2342
 
2274
2453
        except errors.NoSuchFile:
2275
2454
            pass
2276
2455
        branch.set_bound_location(None)
 
2456
 
 
2457
 
 
2458
class Converter6to7(object):
 
2459
    """Perform an in-place upgrade of format 6 to format 7"""
 
2460
 
 
2461
    def convert(self, branch):
 
2462
        format = BzrBranchFormat7()
 
2463
        branch._set_config_location('stacked_on_location', '')
 
2464
        # update target format
 
2465
        branch._transport.put_bytes('format', format.get_format_string())