~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2008-10-30 00:55:00 UTC
  • mto: (3815.2.5 prepare-1.9)
  • mto: This revision was merged to the branch mainline in revision 3811.
  • Revision ID: john@arbash-meinel.com-20081030005500-r5cej1cxflqhs3io
Switch so that we are using a simple timestamp as the first action.

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 RepositoryFormatKnitPack5RichRoot
36
40
from bzrlib.tag import (
37
41
    BasicTags,
38
42
    DisabledTags,
85
89
        self.tags = self._make_tags()
86
90
        self._revision_history_cache = None
87
91
        self._revision_id_to_revno_cache = None
 
92
        self._last_revision_info_cache = None
 
93
        self._open_hook()
 
94
        hooks = Branch.hooks['open']
 
95
        for hook in hooks:
 
96
            hook(self)
 
97
 
 
98
    def _open_hook(self):
 
99
        """Called by init to allow simpler extension of the base class."""
88
100
 
89
101
    def break_lock(self):
90
102
        """Break a lock if one is present from another instance.
222
234
        """
223
235
        self.control_files.dont_leave_in_place()
224
236
 
225
 
    @deprecated_method(deprecated_in((0, 16, 0)))
226
 
    def abspath(self, name):
227
 
        """Return absolute filename for something in the branch
228
 
        
229
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
230
 
        method and not a tree method.
231
 
        """
232
 
        raise NotImplementedError(self.abspath)
233
 
 
234
237
    def bind(self, other):
235
238
        """Bind the local branch the other branch.
236
239
 
324
327
            raise errors.InvalidRevisionNumber(revno)
325
328
        return self.repository.get_revision_delta(rh[revno-1])
326
329
 
 
330
    def get_stacked_on_url(self):
 
331
        """Get the URL this branch is stacked against.
 
332
 
 
333
        :raises NotStacked: If the branch is not stacked.
 
334
        :raises UnstackableBranchFormat: If the branch does not support
 
335
            stacking.
 
336
        """
 
337
        raise NotImplementedError(self.get_stacked_on_url)
 
338
 
327
339
    def print_file(self, file, revision_id):
328
340
        """Print `file` to stdout."""
329
341
        raise NotImplementedError(self.print_file)
331
343
    def set_revision_history(self, rev_history):
332
344
        raise NotImplementedError(self.set_revision_history)
333
345
 
 
346
    def set_stacked_on_url(self, url):
 
347
        """Set the URL this branch is stacked against.
 
348
 
 
349
        :raises UnstackableBranchFormat: If the branch does not support
 
350
            stacking.
 
351
        :raises UnstackableRepositoryFormat: If the repository does not support
 
352
            stacking.
 
353
        """
 
354
        raise NotImplementedError(self.set_stacked_on_url)
 
355
 
334
356
    def _cache_revision_history(self, rev_history):
335
357
        """Set the cached revision history to rev_history.
336
358
 
361
383
        """
362
384
        self._revision_history_cache = None
363
385
        self._revision_id_to_revno_cache = None
 
386
        self._last_revision_info_cache = None
364
387
 
365
388
    def _gen_revision_history(self):
366
389
        """Return sequence of revision hashes on to this branch.
413
436
        """Return last revision id, or NULL_REVISION."""
414
437
        return self.last_revision_info()[1]
415
438
 
 
439
    @needs_read_lock
416
440
    def last_revision_info(self):
417
441
        """Return information about the last revision.
418
442
 
419
 
        :return: A tuple (revno, last_revision_id).
 
443
        :return: A tuple (revno, revision_id).
420
444
        """
 
445
        if self._last_revision_info_cache is None:
 
446
            self._last_revision_info_cache = self._last_revision_info()
 
447
        return self._last_revision_info_cache
 
448
 
 
449
    def _last_revision_info(self):
421
450
        rh = self.revision_history()
422
451
        revno = len(rh)
423
452
        if revno:
484
513
            if not overwrite:
485
514
                if graph is None:
486
515
                    graph = self.repository.get_graph()
487
 
                heads = graph.heads([stop_revision, last_rev])
488
 
                if heads == set([last_rev]):
489
 
                    # The current revision is a decendent of the target,
490
 
                    # nothing to do
 
516
                if self._check_if_descendant_or_diverged(
 
517
                        stop_revision, last_rev, graph, other):
 
518
                    # stop_revision is a descendant of last_rev, but we aren't
 
519
                    # overwriting, so we're done.
491
520
                    return
492
 
                elif heads == set([stop_revision, last_rev]):
493
 
                    # These branches have diverged
494
 
                    raise errors.DivergedBranches(self, other)
495
 
                elif heads != set([stop_revision]):
496
 
                    raise AssertionError("invalid heads: %r" % heads)
497
521
            if stop_revno is None:
498
522
                if graph is None:
499
523
                    graph = self.repository.get_graph()
505
529
        finally:
506
530
            other.unlock()
507
531
 
508
 
 
509
 
 
510
532
    def revision_id_to_revno(self, revision_id):
511
533
        """Given a revision id, return its revno"""
512
534
        if _mod_revision.is_null(revision_id):
528
550
        return history[revno - 1]
529
551
 
530
552
    def pull(self, source, overwrite=False, stop_revision=None,
531
 
             possible_transports=None):
 
553
             possible_transports=None, _override_hook_target=None):
532
554
        """Mirror source into this branch.
533
555
 
534
556
        This branch is considered to be 'local', having low latency.
548
570
        """Return `Tree` object for last revision."""
549
571
        return self.repository.revision_tree(self.last_revision())
550
572
 
551
 
    def rename_one(self, from_rel, to_rel):
552
 
        """Rename one file.
553
 
 
554
 
        This can change the directory or the filename or both.
555
 
        """
556
 
        raise NotImplementedError(self.rename_one)
557
 
 
558
 
    def move(self, from_paths, to_name):
559
 
        """Rename files.
560
 
 
561
 
        to_name must exist as a versioned directory.
562
 
 
563
 
        If to_name exists and is a directory, the files are moved into
564
 
        it, keeping their old names.  If it is a directory, 
565
 
 
566
 
        Note that to_name is only the last component of the new name;
567
 
        this doesn't change the directory.
568
 
 
569
 
        This returns a list of (from_path, to_path) pairs for each
570
 
        entry that is moved.
571
 
        """
572
 
        raise NotImplementedError(self.move)
573
 
 
574
573
    def get_parent(self):
575
574
        """Return the parent location of the branch.
576
575
 
676
675
        revision_id: if not None, the revision history in the new branch will
677
676
                     be truncated to end with revision_id.
678
677
        """
679
 
        result = self._format.initialize(to_bzrdir)
 
678
        result = to_bzrdir.create_branch()
680
679
        self.copy_content_into(result, revision_id=revision_id)
681
680
        return  result
682
681
 
683
682
    @needs_read_lock
684
683
    def sprout(self, to_bzrdir, revision_id=None):
685
684
        """Create a new line of development from the branch, into to_bzrdir.
686
 
        
 
685
 
 
686
        to_bzrdir controls the branch format.
 
687
 
687
688
        revision_id: if not None, the revision history in the new branch will
688
689
                     be truncated to end with revision_id.
689
690
        """
690
 
        result = self._format.initialize(to_bzrdir)
 
691
        result = to_bzrdir.create_branch()
691
692
        self.copy_content_into(result, revision_id=revision_id)
692
693
        result.set_parent(self.bzrdir.root_transport.base)
693
694
        return result
707
708
        """
708
709
        if revision_id == _mod_revision.NULL_REVISION:
709
710
            new_history = []
710
 
        new_history = self.revision_history()
 
711
        else:
 
712
            new_history = self.revision_history()
711
713
        if revision_id is not None and new_history != []:
712
714
            try:
713
715
                new_history = new_history[:new_history.index(revision_id) + 1]
854
856
    def supports_tags(self):
855
857
        return self._format.supports_tags()
856
858
 
 
859
    def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
 
860
                                         other_branch):
 
861
        """Ensure that revision_b is a descendant of revision_a.
 
862
 
 
863
        This is a helper function for update_revisions.
 
864
        
 
865
        :raises: DivergedBranches if revision_b has diverged from revision_a.
 
866
        :returns: True if revision_b is a descendant of revision_a.
 
867
        """
 
868
        relation = self._revision_relations(revision_a, revision_b, graph)
 
869
        if relation == 'b_descends_from_a':
 
870
            return True
 
871
        elif relation == 'diverged':
 
872
            raise errors.DivergedBranches(self, other_branch)
 
873
        elif relation == 'a_descends_from_b':
 
874
            return False
 
875
        else:
 
876
            raise AssertionError("invalid relation: %r" % (relation,))
 
877
 
 
878
    def _revision_relations(self, revision_a, revision_b, graph):
 
879
        """Determine the relationship between two revisions.
 
880
        
 
881
        :returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
 
882
        """
 
883
        heads = graph.heads([revision_a, revision_b])
 
884
        if heads == set([revision_b]):
 
885
            return 'b_descends_from_a'
 
886
        elif heads == set([revision_a, revision_b]):
 
887
            # These branches have diverged
 
888
            return 'diverged'
 
889
        elif heads == set([revision_a]):
 
890
            return 'a_descends_from_b'
 
891
        else:
 
892
            raise AssertionError("invalid heads: %r" % (heads,))
 
893
 
857
894
 
858
895
class BranchFormat(object):
859
896
    """An encapsulation of the initialization and open routines for a format.
998
1035
    def set_default_format(klass, format):
999
1036
        klass._default_format = format
1000
1037
 
 
1038
    def supports_stacking(self):
 
1039
        """True if this format records a stacked-on branch."""
 
1040
        return False
 
1041
 
1001
1042
    @classmethod
1002
1043
    def unregister_format(klass, format):
1003
1044
        del klass._formats[format.get_format_string()]
1030
1071
        # (branch, revision_history), and the branch will
1031
1072
        # be write-locked.
1032
1073
        self['set_rh'] = []
 
1074
        # Invoked after a branch is opened. The api signature is (branch).
 
1075
        self['open'] = []
1033
1076
        # invoked after a push operation completes.
1034
1077
        # the api signature is
1035
1078
        # (push_result)
1071
1114
        # local is the local branch or None, master is the target branch,
1072
1115
        # and an empty branch recieves new_revno of 0, new_revid of None.
1073
1116
        self['post_uncommit'] = []
 
1117
        # Introduced in 1.6
 
1118
        # Invoked before the tip of a branch changes.
 
1119
        # the api signature is
 
1120
        # (params) where params is a ChangeBranchTipParams with the members
 
1121
        # (branch, old_revno, new_revno, old_revid, new_revid)
 
1122
        self['pre_change_branch_tip'] = []
1074
1123
        # Introduced in 1.4
1075
1124
        # Invoked after the tip of a branch changes.
1076
1125
        # the api signature is
1077
1126
        # (params) where params is a ChangeBranchTipParams with the members
1078
1127
        # (branch, old_revno, new_revno, old_revid, new_revid)
1079
1128
        self['post_change_branch_tip'] = []
 
1129
        # Introduced in 1.9
 
1130
        # Invoked when a stacked branch activates its fallback locations and
 
1131
        # allows the transformation of the url of said location.
 
1132
        # the api signature is
 
1133
        # (branch, url) where branch is the branch having its fallback
 
1134
        # location activated and url is the url for the fallback location.
 
1135
        # The hook should return a url.
 
1136
        self['transform_fallback_location'] = []
1080
1137
 
1081
1138
 
1082
1139
# install the default hooks into the Branch class.
1112
1169
        self.old_revid = old_revid
1113
1170
        self.new_revid = new_revid
1114
1171
 
 
1172
    def __eq__(self, other):
 
1173
        return self.__dict__ == other.__dict__
 
1174
    
 
1175
    def __repr__(self):
 
1176
        return "<%s of %s from (%s, %s) to (%s, %s)>" % (
 
1177
            self.__class__.__name__, self.branch, 
 
1178
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
 
1179
 
1115
1180
 
1116
1181
class BzrBranchFormat4(BranchFormat):
1117
1182
    """Bzr branch format 4.
1155
1220
        return "Bazaar-NG branch format 4"
1156
1221
 
1157
1222
 
1158
 
class BzrBranchFormat5(BranchFormat):
 
1223
class BranchFormatMetadir(BranchFormat):
 
1224
    """Common logic for meta-dir based branch formats."""
 
1225
 
 
1226
    def _branch_class(self):
 
1227
        """What class to instantiate on open calls."""
 
1228
        raise NotImplementedError(self._branch_class)
 
1229
 
 
1230
    def open(self, a_bzrdir, _found=False):
 
1231
        """Return the branch object for a_bzrdir.
 
1232
 
 
1233
        _found is a private parameter, do not use it. It is used to indicate
 
1234
               if format probing has already be done.
 
1235
        """
 
1236
        if not _found:
 
1237
            format = BranchFormat.find_format(a_bzrdir)
 
1238
            if format.__class__ != self.__class__:
 
1239
                raise AssertionError("wrong format %r found for %r" %
 
1240
                    (format, self))
 
1241
        try:
 
1242
            transport = a_bzrdir.get_branch_transport(None)
 
1243
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
1244
                                                         lockdir.LockDir)
 
1245
            return self._branch_class()(_format=self,
 
1246
                              _control_files=control_files,
 
1247
                              a_bzrdir=a_bzrdir,
 
1248
                              _repository=a_bzrdir.find_repository())
 
1249
        except errors.NoSuchFile:
 
1250
            raise errors.NotBranchError(path=transport.base)
 
1251
 
 
1252
    def __init__(self):
 
1253
        super(BranchFormatMetadir, self).__init__()
 
1254
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1255
 
 
1256
    def supports_tags(self):
 
1257
        return True
 
1258
 
 
1259
 
 
1260
class BzrBranchFormat5(BranchFormatMetadir):
1159
1261
    """Bzr branch format 5.
1160
1262
 
1161
1263
    This format has:
1168
1270
    This format is new in bzr 0.8.
1169
1271
    """
1170
1272
 
 
1273
    def _branch_class(self):
 
1274
        return BzrBranch5
 
1275
 
1171
1276
    def get_format_string(self):
1172
1277
        """See BranchFormat.get_format_string()."""
1173
1278
        return "Bazaar-NG branch format 5\n"
1183
1288
                      ]
1184
1289
        return self._initialize_helper(a_bzrdir, utf8_files)
1185
1290
 
1186
 
    def __init__(self):
1187
 
        super(BzrBranchFormat5, self).__init__()
1188
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1189
 
 
1190
 
    def open(self, a_bzrdir, _found=False):
1191
 
        """Return the branch object for a_bzrdir
1192
 
 
1193
 
        _found is a private parameter, do not use it. It is used to indicate
1194
 
               if format probing has already be done.
1195
 
        """
1196
 
        if not _found:
1197
 
            format = BranchFormat.find_format(a_bzrdir)
1198
 
            if format.__class__ != self.__class__:
1199
 
                raise AssertionError("wrong format %r found for %r" %
1200
 
                    (format, self))
1201
 
        try:
1202
 
            transport = a_bzrdir.get_branch_transport(None)
1203
 
            control_files = lockable_files.LockableFiles(transport, 'lock',
1204
 
                                                         lockdir.LockDir)
1205
 
            return BzrBranch5(_format=self,
1206
 
                              _control_files=control_files,
1207
 
                              a_bzrdir=a_bzrdir,
1208
 
                              _repository=a_bzrdir.find_repository())
1209
 
        except errors.NoSuchFile:
1210
 
            raise errors.NotBranchError(path=transport.base)
1211
 
 
1212
 
 
1213
 
class BzrBranchFormat6(BzrBranchFormat5):
 
1291
    def supports_tags(self):
 
1292
        return False
 
1293
 
 
1294
 
 
1295
class BzrBranchFormat6(BranchFormatMetadir):
1214
1296
    """Branch format with last-revision and tags.
1215
1297
 
1216
1298
    Unlike previous formats, this has no explicit revision history. Instead,
1221
1303
    and became the default in 0.91.
1222
1304
    """
1223
1305
 
 
1306
    def _branch_class(self):
 
1307
        return BzrBranch6
 
1308
 
1224
1309
    def get_format_string(self):
1225
1310
        """See BranchFormat.get_format_string()."""
1226
1311
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1237
1322
                      ]
1238
1323
        return self._initialize_helper(a_bzrdir, utf8_files)
1239
1324
 
1240
 
    def open(self, a_bzrdir, _found=False):
1241
 
        """Return the branch object for a_bzrdir
1242
 
 
1243
 
        _found is a private parameter, do not use it. It is used to indicate
1244
 
               if format probing has already be done.
1245
 
        """
1246
 
        if not _found:
1247
 
            format = BranchFormat.find_format(a_bzrdir)
1248
 
            if format.__class__ != self.__class__:
1249
 
                raise AssertionError("wrong format %r found for %r" %
1250
 
                    (format, self))
1251
 
        transport = a_bzrdir.get_branch_transport(None)
1252
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1253
 
                                                     lockdir.LockDir)
1254
 
        return BzrBranch6(_format=self,
1255
 
                          _control_files=control_files,
1256
 
                          a_bzrdir=a_bzrdir,
1257
 
                          _repository=a_bzrdir.find_repository())
1258
 
 
1259
 
    def supports_tags(self):
 
1325
 
 
1326
class BzrBranchFormat7(BranchFormatMetadir):
 
1327
    """Branch format with last-revision, tags, and a stacked location pointer.
 
1328
 
 
1329
    The stacked location pointer is passed down to the repository and requires
 
1330
    a repository format with supports_external_lookups = True.
 
1331
 
 
1332
    This format was introduced in bzr 1.6.
 
1333
    """
 
1334
 
 
1335
    def _branch_class(self):
 
1336
        return BzrBranch7
 
1337
 
 
1338
    def get_format_string(self):
 
1339
        """See BranchFormat.get_format_string()."""
 
1340
        return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
 
1341
 
 
1342
    def get_format_description(self):
 
1343
        """See BranchFormat.get_format_description()."""
 
1344
        return "Branch format 7"
 
1345
 
 
1346
    def initialize(self, a_bzrdir):
 
1347
        """Create a branch of this format in a_bzrdir."""
 
1348
        utf8_files = [('last-revision', '0 null:\n'),
 
1349
                      ('branch.conf', ''),
 
1350
                      ('tags', ''),
 
1351
                      ]
 
1352
        return self._initialize_helper(a_bzrdir, utf8_files)
 
1353
 
 
1354
    def __init__(self):
 
1355
        super(BzrBranchFormat7, self).__init__()
 
1356
        self._matchingbzrdir.repository_format = \
 
1357
            RepositoryFormatKnitPack5RichRoot()
 
1358
 
 
1359
    def supports_stacking(self):
1260
1360
        return True
1261
1361
 
1262
1362
 
1351
1451
# and not independently creatable, so are not registered.
1352
1452
__format5 = BzrBranchFormat5()
1353
1453
__format6 = BzrBranchFormat6()
 
1454
__format7 = BzrBranchFormat7()
1354
1455
BranchFormat.register_format(__format5)
1355
1456
BranchFormat.register_format(BranchReferenceFormat())
1356
1457
BranchFormat.register_format(__format6)
 
1458
BranchFormat.register_format(__format7)
1357
1459
BranchFormat.set_default_format(__format6)
1358
1460
_legacy_formats = [BzrBranchFormat4(),
1359
1461
                   ]
1375
1477
    def __init__(self, _format=None,
1376
1478
                 _control_files=None, a_bzrdir=None, _repository=None):
1377
1479
        """Create new branch object at a particular location."""
1378
 
        Branch.__init__(self)
1379
1480
        if a_bzrdir is None:
1380
1481
            raise ValueError('a_bzrdir must be supplied')
1381
1482
        else:
1390
1491
        self.control_files = _control_files
1391
1492
        self._transport = _control_files._transport
1392
1493
        self.repository = _repository
 
1494
        Branch.__init__(self)
1393
1495
 
1394
1496
    def __str__(self):
1395
1497
        return '%s(%r)' % (self.__class__.__name__, self.base)
1402
1504
 
1403
1505
    base = property(_get_base, doc="The URL for the root of this branch.")
1404
1506
 
1405
 
    @deprecated_method(deprecated_in((0, 16, 0)))
1406
 
    def abspath(self, name):
1407
 
        """See Branch.abspath."""
1408
 
        return self._transport.abspath(name)
1409
 
 
1410
1507
    def is_locked(self):
1411
1508
        return self.control_files.is_locked()
1412
1509
 
1465
1562
        """See Branch.set_revision_history."""
1466
1563
        if 'evil' in debug.debug_flags:
1467
1564
            mutter_callsite(3, "set_revision_history scales with history.")
 
1565
        check_not_reserved_id = _mod_revision.check_not_reserved_id
 
1566
        for rev_id in rev_history:
 
1567
            check_not_reserved_id(rev_id)
 
1568
        if Branch.hooks['post_change_branch_tip']:
 
1569
            # Don't calculate the last_revision_info() if there are no hooks
 
1570
            # that will use it.
 
1571
            old_revno, old_revid = self.last_revision_info()
 
1572
        if len(rev_history) == 0:
 
1573
            revid = _mod_revision.NULL_REVISION
 
1574
        else:
 
1575
            revid = rev_history[-1]
 
1576
        self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
1468
1577
        self._write_revision_history(rev_history)
1469
1578
        self._clear_cached_state()
1470
1579
        self._cache_revision_history(rev_history)
1471
1580
        for hook in Branch.hooks['set_rh']:
1472
1581
            hook(self, rev_history)
 
1582
        if Branch.hooks['post_change_branch_tip']:
 
1583
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1473
1584
 
 
1585
    def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
 
1586
        """Run the pre_change_branch_tip hooks."""
 
1587
        hooks = Branch.hooks['pre_change_branch_tip']
 
1588
        if not hooks:
 
1589
            return
 
1590
        old_revno, old_revid = self.last_revision_info()
 
1591
        params = ChangeBranchTipParams(
 
1592
            self, old_revno, new_revno, old_revid, new_revid)
 
1593
        for hook in hooks:
 
1594
            try:
 
1595
                hook(params)
 
1596
            except errors.TipChangeRejected:
 
1597
                raise
 
1598
            except Exception:
 
1599
                exc_info = sys.exc_info()
 
1600
                hook_name = Branch.hooks.get_hook_name(hook)
 
1601
                raise errors.HookFailed(
 
1602
                    'pre_change_branch_tip', hook_name, exc_info)
 
1603
 
1474
1604
    def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1475
1605
        """Run the post_change_branch_tip hooks."""
1476
1606
        hooks = Branch.hooks['post_change_branch_tip']
1495
1625
        be permitted.
1496
1626
        """
1497
1627
        revision_id = _mod_revision.ensure_null(revision_id)
1498
 
        old_revno, old_revid = self.last_revision_info()
1499
1628
        # this old format stores the full history, but this api doesn't
1500
1629
        # provide it, so we must generate, and might as well check it's
1501
1630
        # correct
1503
1632
        if len(history) != revno:
1504
1633
            raise AssertionError('%d != %d' % (len(history), revno))
1505
1634
        self.set_revision_history(history)
1506
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1507
1635
 
1508
1636
    def _gen_revision_history(self):
1509
1637
        history = self._transport.get_bytes('revision-history').split('\n')
1528
1656
            raise errors.NoSuchRevision(self, revision_id)
1529
1657
        current_rev_id = revision_id
1530
1658
        new_history = []
 
1659
        check_not_reserved_id = _mod_revision.check_not_reserved_id
1531
1660
        # Do not include ghosts or graph origin in revision_history
1532
1661
        while (current_rev_id in parents_map and
1533
1662
               len(parents_map[current_rev_id]) > 0):
 
1663
            check_not_reserved_id(current_rev_id)
1534
1664
            new_history.append(current_rev_id)
1535
1665
            current_rev_id = parents_map[current_rev_id][0]
1536
1666
            parents_map = graph.get_parent_map([current_rev_id])
1557
1687
 
1558
1688
    @needs_write_lock
1559
1689
    def pull(self, source, overwrite=False, stop_revision=None,
1560
 
             _hook_master=None, run_hooks=True, possible_transports=None):
 
1690
             _hook_master=None, run_hooks=True, possible_transports=None,
 
1691
             _override_hook_target=None):
1561
1692
        """See Branch.pull.
1562
1693
 
1563
1694
        :param _hook_master: Private parameter - set the branch to 
1564
 
            be supplied as the master to push hooks.
 
1695
            be supplied as the master to pull hooks.
1565
1696
        :param run_hooks: Private parameter - if false, this branch
1566
1697
            is being called because it's the master of the primary branch,
1567
1698
            so it should not run its hooks.
 
1699
        :param _override_hook_target: Private parameter - set the branch to be
 
1700
            supplied as the target_branch to pull hooks.
1568
1701
        """
1569
1702
        result = PullResult()
1570
1703
        result.source_branch = source
1571
 
        result.target_branch = self
 
1704
        if _override_hook_target is None:
 
1705
            result.target_branch = self
 
1706
        else:
 
1707
            result.target_branch = _override_hook_target
1572
1708
        source.lock_read()
1573
1709
        try:
1574
1710
            # We assume that during 'pull' the local repository is closer than
1581
1717
            result.new_revno, result.new_revid = self.last_revision_info()
1582
1718
            if _hook_master:
1583
1719
                result.master_branch = _hook_master
1584
 
                result.local_branch = self
 
1720
                result.local_branch = result.target_branch
1585
1721
            else:
1586
 
                result.master_branch = self
 
1722
                result.master_branch = result.target_branch
1587
1723
                result.local_branch = None
1588
1724
            if run_hooks:
1589
1725
                for hook in Branch.hooks['post_pull']:
1615
1751
        """
1616
1752
        # TODO: Public option to disable running hooks - should be trivial but
1617
1753
        # needs tests.
1618
 
        target.lock_write()
1619
 
        try:
1620
 
            result = self._push_with_bound_branches(target, overwrite,
1621
 
                    stop_revision,
1622
 
                    _override_hook_source_branch=_override_hook_source_branch)
1623
 
            return result
1624
 
        finally:
1625
 
            target.unlock()
 
1754
        return _run_with_write_locked_target(
 
1755
            target, self._push_with_bound_branches, target, overwrite,
 
1756
            stop_revision,
 
1757
            _override_hook_source_branch=_override_hook_source_branch)
1626
1758
 
1627
1759
    def _push_with_bound_branches(self, target, overwrite,
1628
1760
            stop_revision,
1679
1811
        result.source_branch = self
1680
1812
        result.target_branch = target
1681
1813
        result.old_revno, result.old_revid = target.last_revision_info()
1682
 
 
1683
 
        # We assume that during 'push' this repository is closer than
1684
 
        # the target.
1685
 
        graph = self.repository.get_graph(target.repository)
1686
 
        target.update_revisions(self, stop_revision, overwrite=overwrite,
1687
 
                                graph=graph)
1688
 
        result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
 
1814
        if result.old_revid != self.last_revision():
 
1815
            # We assume that during 'push' this repository is closer than
 
1816
            # the target.
 
1817
            graph = self.repository.get_graph(target.repository)
 
1818
            target.update_revisions(self, stop_revision, overwrite=overwrite,
 
1819
                                    graph=graph)
 
1820
        if self._push_should_merge_tags():
 
1821
            result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1689
1822
        result.new_revno, result.new_revid = target.last_revision_info()
1690
1823
        return result
1691
1824
 
 
1825
    def _push_should_merge_tags(self):
 
1826
        """Should _basic_push merge this branch's tags into the target?
 
1827
        
 
1828
        The default implementation returns False if this branch has no tags,
 
1829
        and True the rest of the time.  Subclasses may override this.
 
1830
        """
 
1831
        return self.tags.supports_tags() and self.tags.get_tag_dict()
 
1832
 
1692
1833
    def get_parent(self):
1693
1834
        """See Branch.get_parent."""
1694
1835
        parent = self._get_parent_location()
1703
1844
        except errors.InvalidURLJoin, e:
1704
1845
            raise errors.InaccessibleParent(parent, self.base)
1705
1846
 
 
1847
    def get_stacked_on_url(self):
 
1848
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
1849
 
1706
1850
    def set_push_location(self, location):
1707
1851
        """See Branch.set_push_location."""
1708
1852
        self.get_config().set_user_option(
1734
1878
            self._transport.put_bytes('parent', url + '\n',
1735
1879
                mode=self.bzrdir._get_file_mode())
1736
1880
 
 
1881
    def set_stacked_on_url(self, url):
 
1882
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
1883
 
1737
1884
 
1738
1885
class BzrBranch5(BzrBranch):
1739
1886
    """A format 5 branch. This supports new features over plain branches.
1741
1888
    It has support for a master_branch which is the data for bound branches.
1742
1889
    """
1743
1890
 
1744
 
    def __init__(self,
1745
 
                 _format,
1746
 
                 _control_files,
1747
 
                 a_bzrdir,
1748
 
                 _repository):
1749
 
        super(BzrBranch5, self).__init__(_format=_format,
1750
 
                                         _control_files=_control_files,
1751
 
                                         a_bzrdir=a_bzrdir,
1752
 
                                         _repository=_repository)
1753
 
        
1754
1891
    @needs_write_lock
1755
1892
    def pull(self, source, overwrite=False, stop_revision=None,
1756
 
             run_hooks=True, possible_transports=None):
 
1893
             run_hooks=True, possible_transports=None,
 
1894
             _override_hook_target=None):
1757
1895
        """Pull from source into self, updating my master if any.
1758
1896
        
1759
1897
        :param run_hooks: Private parameter - if false, this branch
1773
1911
                    run_hooks=False)
1774
1912
            return super(BzrBranch5, self).pull(source, overwrite,
1775
1913
                stop_revision, _hook_master=master_branch,
1776
 
                run_hooks=run_hooks)
 
1914
                run_hooks=run_hooks,
 
1915
                _override_hook_target=_override_hook_target)
1777
1916
        finally:
1778
1917
            if master_branch:
1779
1918
                master_branch.unlock()
1870
2009
        return None
1871
2010
 
1872
2011
 
1873
 
class BzrBranch6(BzrBranch5):
 
2012
class BzrBranch7(BzrBranch5):
 
2013
    """A branch with support for a fallback repository."""
 
2014
 
 
2015
    def _get_fallback_repository(self, url):
 
2016
        """Get the repository we fallback to at url."""
 
2017
        url = urlutils.join(self.base, url)
 
2018
        return bzrdir.BzrDir.open(url).open_branch().repository
 
2019
 
 
2020
    def _activate_fallback_location(self, url):
 
2021
        """Activate the branch/repository from url as a fallback repository."""
 
2022
        self.repository.add_fallback_repository(
 
2023
            self._get_fallback_repository(url))
 
2024
 
 
2025
    def _open_hook(self):
 
2026
        try:
 
2027
            url = self.get_stacked_on_url()
 
2028
        except (errors.UnstackableRepositoryFormat, errors.NotStacked,
 
2029
            errors.UnstackableBranchFormat):
 
2030
            pass
 
2031
        else:
 
2032
            for hook in Branch.hooks['transform_fallback_location']:
 
2033
                url = hook(self, url)
 
2034
                if url is None:
 
2035
                    hook_name = Branch.hooks.get_hook_name(hook)
 
2036
                    raise AssertionError(
 
2037
                        "'transform_fallback_location' hook %s returned "
 
2038
                        "None, not a URL." % hook_name)
 
2039
            self._activate_fallback_location(url)
 
2040
 
 
2041
    def _check_stackable_repo(self):
 
2042
        if not self.repository._format.supports_external_lookups:
 
2043
            raise errors.UnstackableRepositoryFormat(self.repository._format,
 
2044
                self.repository.base)
1874
2045
 
1875
2046
    def __init__(self, *args, **kwargs):
1876
 
        super(BzrBranch6, self).__init__(*args, **kwargs)
 
2047
        super(BzrBranch7, self).__init__(*args, **kwargs)
1877
2048
        self._last_revision_info_cache = None
1878
2049
        self._partial_revision_history_cache = []
1879
2050
 
1880
2051
    def _clear_cached_state(self):
1881
 
        super(BzrBranch6, self)._clear_cached_state()
 
2052
        super(BzrBranch7, self)._clear_cached_state()
1882
2053
        self._last_revision_info_cache = None
1883
2054
        self._partial_revision_history_cache = []
1884
2055
 
1885
 
    @needs_read_lock
1886
 
    def last_revision_info(self):
1887
 
        """Return information about the last revision.
1888
 
 
1889
 
        :return: A tuple (revno, revision_id).
1890
 
        """
1891
 
        if self._last_revision_info_cache is None:
1892
 
            self._last_revision_info_cache = self._last_revision_info()
1893
 
        return self._last_revision_info_cache
1894
 
 
1895
2056
    def _last_revision_info(self):
1896
2057
        revision_string = self._transport.get_bytes('last-revision')
1897
2058
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1919
2080
        old_revno, old_revid = self.last_revision_info()
1920
2081
        if self._get_append_revisions_only():
1921
2082
            self._check_history_violation(revision_id)
 
2083
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
1922
2084
        self._write_last_revision_info(revno, revision_id)
1923
2085
        self._clear_cached_state()
1924
2086
        self._last_revision_info_cache = revno, revision_id
1934
2096
    def _gen_revision_history(self):
1935
2097
        """Generate the revision history from last revision
1936
2098
        """
1937
 
        self._extend_partial_history()
 
2099
        last_revno, last_revision = self.last_revision_info()
 
2100
        self._extend_partial_history(stop_index=last_revno-1)
1938
2101
        return list(reversed(self._partial_revision_history_cache))
1939
2102
 
1940
2103
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
2030
2193
        """See Branch.get_old_bound_location"""
2031
2194
        return self._get_bound_location(False)
2032
2195
 
 
2196
    def get_stacked_on_url(self):
 
2197
        # you can always ask for the URL; but you might not be able to use it
 
2198
        # if the repo can't support stacking.
 
2199
        ## self._check_stackable_repo()
 
2200
        stacked_url = self._get_config_location('stacked_on_location')
 
2201
        if stacked_url is None:
 
2202
            raise errors.NotStacked(self)
 
2203
        return stacked_url
 
2204
 
2033
2205
    def set_append_revisions_only(self, enabled):
2034
2206
        if enabled:
2035
2207
            value = 'True'
2038
2210
        self.get_config().set_user_option('append_revisions_only', value,
2039
2211
            warn_masked=True)
2040
2212
 
 
2213
    def set_stacked_on_url(self, url):
 
2214
        self._check_stackable_repo()
 
2215
        if not url:
 
2216
            try:
 
2217
                old_url = self.get_stacked_on_url()
 
2218
            except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2219
                errors.UnstackableRepositoryFormat):
 
2220
                return
 
2221
            url = ''
 
2222
            # repositories don't offer an interface to remove fallback
 
2223
            # repositories today; take the conceptually simpler option and just
 
2224
            # reopen it.
 
2225
            self.repository = self.bzrdir.find_repository()
 
2226
            # for every revision reference the branch has, ensure it is pulled
 
2227
            # in.
 
2228
            source_repository = self._get_fallback_repository(old_url)
 
2229
            for revision_id in chain([self.last_revision()],
 
2230
                self.tags.get_reverse_tag_dict()):
 
2231
                self.repository.fetch(source_repository, revision_id,
 
2232
                    find_ghosts=True)
 
2233
        else:
 
2234
            self._activate_fallback_location(url)
 
2235
        # write this out after the repository is stacked to avoid setting a
 
2236
        # stacked config that doesn't work.
 
2237
        self._set_config_location('stacked_on_location', url)
 
2238
 
2041
2239
    def _get_append_revisions_only(self):
2042
2240
        value = self.get_config().get_user_option('append_revisions_only')
2043
2241
        return value == 'True'
2118
2316
        return self.revno() - index
2119
2317
 
2120
2318
 
 
2319
class BzrBranch6(BzrBranch7):
 
2320
    """See BzrBranchFormat6 for the capabilities of this branch.
 
2321
 
 
2322
    This subclass of BzrBranch7 disables the new features BzrBranch7 added,
 
2323
    i.e. stacking.
 
2324
    """
 
2325
 
 
2326
    def get_stacked_on_url(self):
 
2327
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2328
 
 
2329
    def set_stacked_on_url(self, url):
 
2330
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2331
 
 
2332
 
2121
2333
######################################################################
2122
2334
# results of operations
2123
2335
 
2140
2352
    :ivar old_revid: Tip revision id before pull.
2141
2353
    :ivar new_revid: Tip revision id after pull.
2142
2354
    :ivar source_branch: Source (local) branch object.
2143
 
    :ivar master_branch: Master branch of the target, or None.
 
2355
    :ivar master_branch: Master branch of the target, or the target if no
 
2356
        Master
 
2357
    :ivar local_branch: target branch if there is a Master, else None
2144
2358
    :ivar target_branch: Target/destination branch object.
 
2359
    :ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2145
2360
    """
2146
2361
 
2147
2362
    def __int__(self):
2231
2446
        except errors.NoSuchFile:
2232
2447
            pass
2233
2448
        branch.set_bound_location(None)
 
2449
 
 
2450
 
 
2451
class Converter6to7(object):
 
2452
    """Perform an in-place upgrade of format 6 to format 7"""
 
2453
 
 
2454
    def convert(self, branch):
 
2455
        format = BzrBranchFormat7()
 
2456
        branch._set_config_location('stacked_on_location', '')
 
2457
        # update target format
 
2458
        branch._transport.put_bytes('format', format.get_format_string())
 
2459
 
 
2460
 
 
2461
 
 
2462
def _run_with_write_locked_target(target, callable, *args, **kwargs):
 
2463
    """Run ``callable(*args, **kwargs)``, write-locking target for the
 
2464
    duration.
 
2465
 
 
2466
    _run_with_write_locked_target will attempt to release the lock it acquires.
 
2467
 
 
2468
    If an exception is raised by callable, then that exception *will* be
 
2469
    propagated, even if the unlock attempt raises its own error.  Thus
 
2470
    _run_with_write_locked_target should be preferred to simply doing::
 
2471
 
 
2472
        target.lock_write()
 
2473
        try:
 
2474
            return callable(*args, **kwargs)
 
2475
        finally:
 
2476
            target.unlock()
 
2477
    
 
2478
    """
 
2479
    # This is very similar to bzrlib.decorators.needs_write_lock.  Perhaps they
 
2480
    # should share code?
 
2481
    target.lock_write()
 
2482
    try:
 
2483
        result = callable(*args, **kwargs)
 
2484
    except:
 
2485
        exc_info = sys.exc_info()
 
2486
        try:
 
2487
            target.unlock()
 
2488
        finally:
 
2489
            raise exc_info[0], exc_info[1], exc_info[2]
 
2490
    else:
 
2491
        target.unlock()
 
2492
        return result