/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Andrew Bennetts
  • Date: 2008-09-08 12:59:00 UTC
  • mfrom: (3695 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3756.
  • Revision ID: andrew.bennetts@canonical.com-20080908125900-8ywtsr7jqyyatjz0
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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,
41
45
 
42
46
from bzrlib.decorators import needs_read_lock, needs_write_lock
43
47
from bzrlib.hooks import Hooks
44
 
from bzrlib.symbol_versioning import (deprecated_method,
45
 
                                      zero_sixteen,
46
 
                                      )
 
48
from bzrlib.symbol_versioning import (
 
49
    deprecated_in,
 
50
    deprecated_method,
 
51
    )
47
52
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
48
53
 
49
54
 
84
89
        self.tags = self._make_tags()
85
90
        self._revision_history_cache = None
86
91
        self._revision_id_to_revno_cache = None
 
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."""
87
97
 
88
98
    def break_lock(self):
89
99
        """Break a lock if one is present from another instance.
221
231
        """
222
232
        self.control_files.dont_leave_in_place()
223
233
 
224
 
    def abspath(self, name):
225
 
        """Return absolute filename for something in the branch
226
 
        
227
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
228
 
        method and not a tree method.
229
 
        """
230
 
        raise NotImplementedError(self.abspath)
231
 
 
232
234
    def bind(self, other):
233
235
        """Bind the local branch the other branch.
234
236
 
317
319
        The delta is relative to its mainline predecessor, or the
318
320
        empty tree for revision 1.
319
321
        """
320
 
        assert isinstance(revno, int)
321
322
        rh = self.revision_history()
322
323
        if not (1 <= revno <= len(rh)):
323
324
            raise errors.InvalidRevisionNumber(revno)
324
325
        return self.repository.get_revision_delta(rh[revno-1])
325
326
 
326
 
    @deprecated_method(zero_sixteen)
327
 
    def get_root_id(self):
328
 
        """Return the id of this branches root
 
327
    def get_stacked_on_url(self):
 
328
        """Get the URL this branch is stacked against.
329
329
 
330
 
        Deprecated: branches don't have root ids-- trees do.
331
 
        Use basis_tree().get_root_id() instead.
 
330
        :raises NotStacked: If the branch is not stacked.
 
331
        :raises UnstackableBranchFormat: If the branch does not support
 
332
            stacking.
332
333
        """
333
 
        raise NotImplementedError(self.get_root_id)
 
334
        raise NotImplementedError(self.get_stacked_on_url)
334
335
 
335
336
    def print_file(self, file, revision_id):
336
337
        """Print `file` to stdout."""
339
340
    def set_revision_history(self, rev_history):
340
341
        raise NotImplementedError(self.set_revision_history)
341
342
 
 
343
    def set_stacked_on_url(self, url):
 
344
        """Set the URL this branch is stacked against.
 
345
 
 
346
        :raises UnstackableBranchFormat: If the branch does not support
 
347
            stacking.
 
348
        :raises UnstackableRepositoryFormat: If the repository does not support
 
349
            stacking.
 
350
        """
 
351
        raise NotImplementedError(self.set_stacked_on_url)
 
352
 
342
353
    def _cache_revision_history(self, rev_history):
343
354
        """Set the cached revision history to rev_history.
344
355
 
369
380
        """
370
381
        self._revision_history_cache = None
371
382
        self._revision_id_to_revno_cache = None
 
383
        self._last_revision_info_cache = None
372
384
 
373
385
    def _gen_revision_history(self):
374
386
        """Return sequence of revision hashes on to this branch.
421
433
        """Return last revision id, or NULL_REVISION."""
422
434
        return self.last_revision_info()[1]
423
435
 
 
436
    @needs_read_lock
424
437
    def last_revision_info(self):
425
438
        """Return information about the last revision.
426
439
 
427
 
        :return: A tuple (revno, last_revision_id).
 
440
        :return: A tuple (revno, revision_id).
428
441
        """
 
442
        if self._last_revision_info_cache is None:
 
443
            self._last_revision_info_cache = self._last_revision_info()
 
444
        return self._last_revision_info_cache
 
445
 
 
446
    def _last_revision_info(self):
429
447
        rh = self.revision_history()
430
448
        revno = len(rh)
431
449
        if revno:
433
451
        else:
434
452
            return (0, _mod_revision.NULL_REVISION)
435
453
 
 
454
    @deprecated_method(deprecated_in((1, 6, 0)))
436
455
    def missing_revisions(self, other, stop_revision=None):
437
456
        """Return a list of new revisions that would perfectly fit.
438
457
        
451
470
        if stop_revision is None:
452
471
            stop_revision = other_len
453
472
        else:
454
 
            assert isinstance(stop_revision, int)
455
473
            if stop_revision > other_len:
456
474
                raise errors.NoSuchRevision(self, stop_revision)
457
475
        return other_history[self_len:stop_revision]
458
476
 
459
 
    def update_revisions(self, other, stop_revision=None):
 
477
    @needs_write_lock
 
478
    def update_revisions(self, other, stop_revision=None, overwrite=False,
 
479
                         graph=None):
460
480
        """Pull in new perfect-fit revisions.
461
481
 
462
482
        :param other: Another Branch to pull from
463
483
        :param stop_revision: Updated until the given revision
 
484
        :param overwrite: Always set the branch pointer, rather than checking
 
485
            to see if it is a proper descendant.
 
486
        :param graph: A Graph object that can be used to query history
 
487
            information. This can be None.
464
488
        :return: None
465
489
        """
466
 
        raise NotImplementedError(self.update_revisions)
 
490
        other.lock_read()
 
491
        try:
 
492
            other_revno, other_last_revision = other.last_revision_info()
 
493
            stop_revno = None # unknown
 
494
            if stop_revision is None:
 
495
                stop_revision = other_last_revision
 
496
                if _mod_revision.is_null(stop_revision):
 
497
                    # if there are no commits, we're done.
 
498
                    return
 
499
                stop_revno = other_revno
 
500
 
 
501
            # what's the current last revision, before we fetch [and change it
 
502
            # possibly]
 
503
            last_rev = _mod_revision.ensure_null(self.last_revision())
 
504
            # we fetch here so that we don't process data twice in the common
 
505
            # case of having something to pull, and so that the check for 
 
506
            # already merged can operate on the just fetched graph, which will
 
507
            # be cached in memory.
 
508
            self.fetch(other, stop_revision)
 
509
            # Check to see if one is an ancestor of the other
 
510
            if not overwrite:
 
511
                if graph is None:
 
512
                    graph = self.repository.get_graph()
 
513
                if self._check_if_descendant_or_diverged(
 
514
                        stop_revision, last_rev, graph, other):
 
515
                    # stop_revision is a descendant of last_rev, but we aren't
 
516
                    # overwriting, so we're done.
 
517
                    return
 
518
            if stop_revno is None:
 
519
                if graph is None:
 
520
                    graph = self.repository.get_graph()
 
521
                this_revno, this_last_revision = self.last_revision_info()
 
522
                stop_revno = graph.find_distance_to_null(stop_revision,
 
523
                                [(other_last_revision, other_revno),
 
524
                                 (this_last_revision, this_revno)])
 
525
            self.set_last_revision_info(stop_revno, stop_revision)
 
526
        finally:
 
527
            other.unlock()
467
528
 
468
529
    def revision_id_to_revno(self, revision_id):
469
530
        """Given a revision id, return its revno"""
486
547
        return history[revno - 1]
487
548
 
488
549
    def pull(self, source, overwrite=False, stop_revision=None,
489
 
             possible_transports=None):
 
550
             possible_transports=None, _override_hook_target=None):
490
551
        """Mirror source into this branch.
491
552
 
492
553
        This branch is considered to be 'local', having low latency.
506
567
        """Return `Tree` object for last revision."""
507
568
        return self.repository.revision_tree(self.last_revision())
508
569
 
509
 
    def rename_one(self, from_rel, to_rel):
510
 
        """Rename one file.
511
 
 
512
 
        This can change the directory or the filename or both.
513
 
        """
514
 
        raise NotImplementedError(self.rename_one)
515
 
 
516
 
    def move(self, from_paths, to_name):
517
 
        """Rename files.
518
 
 
519
 
        to_name must exist as a versioned directory.
520
 
 
521
 
        If to_name exists and is a directory, the files are moved into
522
 
        it, keeping their old names.  If it is a directory, 
523
 
 
524
 
        Note that to_name is only the last component of the new name;
525
 
        this doesn't change the directory.
526
 
 
527
 
        This returns a list of (from_path, to_path) pairs for each
528
 
        entry that is moved.
529
 
        """
530
 
        raise NotImplementedError(self.move)
531
 
 
532
570
    def get_parent(self):
533
571
        """Return the parent location of the branch.
534
572
 
634
672
        revision_id: if not None, the revision history in the new branch will
635
673
                     be truncated to end with revision_id.
636
674
        """
637
 
        result = self._format.initialize(to_bzrdir)
 
675
        result = to_bzrdir.create_branch()
638
676
        self.copy_content_into(result, revision_id=revision_id)
639
677
        return  result
640
678
 
641
679
    @needs_read_lock
642
680
    def sprout(self, to_bzrdir, revision_id=None):
643
681
        """Create a new line of development from the branch, into to_bzrdir.
644
 
        
 
682
 
 
683
        to_bzrdir controls the branch format.
 
684
 
645
685
        revision_id: if not None, the revision history in the new branch will
646
686
                     be truncated to end with revision_id.
647
687
        """
648
 
        result = self._format.initialize(to_bzrdir)
 
688
        result = to_bzrdir.create_branch()
649
689
        self.copy_content_into(result, revision_id=revision_id)
650
690
        result.set_parent(self.bzrdir.root_transport.base)
651
691
        return result
665
705
        """
666
706
        if revision_id == _mod_revision.NULL_REVISION:
667
707
            new_history = []
668
 
        new_history = self.revision_history()
 
708
        else:
 
709
            new_history = self.revision_history()
669
710
        if revision_id is not None and new_history != []:
670
711
            try:
671
712
                new_history = new_history[:new_history.index(revision_id) + 1]
704
745
        :return: A BranchCheckResult.
705
746
        """
706
747
        mainline_parent_id = None
707
 
        for revision_id in self.revision_history():
 
748
        last_revno, last_revision_id = self.last_revision_info()
 
749
        real_rev_history = list(self.repository.iter_reverse_revision_history(
 
750
                                last_revision_id))
 
751
        real_rev_history.reverse()
 
752
        if len(real_rev_history) != last_revno:
 
753
            raise errors.BzrCheckError('revno does not match len(mainline)'
 
754
                ' %s != %s' % (last_revno, len(real_rev_history)))
 
755
        # TODO: We should probably also check that real_rev_history actually
 
756
        #       matches self.revision_history()
 
757
        for revision_id in real_rev_history:
708
758
            try:
709
759
                revision = self.repository.get_revision(revision_id)
710
760
            except errors.NoSuchRevision, e:
783
833
            basis_tree.unlock()
784
834
        return tree
785
835
 
 
836
    @needs_write_lock
 
837
    def reconcile(self, thorough=True):
 
838
        """Make sure the data stored in this branch is consistent."""
 
839
        from bzrlib.reconcile import BranchReconciler
 
840
        reconciler = BranchReconciler(self, thorough=thorough)
 
841
        reconciler.reconcile()
 
842
        return reconciler
 
843
 
786
844
    def reference_parent(self, file_id, path):
787
845
        """Return the parent branch for a tree-reference file_id
788
846
        :param file_id: The file_id of the tree reference
795
853
    def supports_tags(self):
796
854
        return self._format.supports_tags()
797
855
 
 
856
    def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
 
857
                                         other_branch):
 
858
        """Ensure that revision_b is a descendant of revision_a.
 
859
 
 
860
        This is a helper function for update_revisions.
 
861
        
 
862
        :raises: DivergedBranches if revision_b has diverged from revision_a.
 
863
        :returns: True if revision_b is a descendant of revision_a.
 
864
        """
 
865
        relation = self._revision_relations(revision_a, revision_b, graph)
 
866
        if relation == 'b_descends_from_a':
 
867
            return True
 
868
        elif relation == 'diverged':
 
869
            raise errors.DivergedBranches(self, other_branch)
 
870
        elif relation == 'a_descends_from_b':
 
871
            return False
 
872
        else:
 
873
            raise AssertionError("invalid relation: %r" % (relation,))
 
874
 
 
875
    def _revision_relations(self, revision_a, revision_b, graph):
 
876
        """Determine the relationship between two revisions.
 
877
        
 
878
        :returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
 
879
        """
 
880
        heads = graph.heads([revision_a, revision_b])
 
881
        if heads == set([revision_b]):
 
882
            return 'b_descends_from_a'
 
883
        elif heads == set([revision_a, revision_b]):
 
884
            # These branches have diverged
 
885
            return 'diverged'
 
886
        elif heads == set([revision_a]):
 
887
            return 'a_descends_from_b'
 
888
        else:
 
889
            raise AssertionError("invalid heads: %r" % (heads,))
 
890
 
798
891
 
799
892
class BranchFormat(object):
800
893
    """An encapsulation of the initialization and open routines for a format.
900
993
        control_files.create_lock()
901
994
        control_files.lock_write()
902
995
        if set_format:
903
 
            control_files.put_utf8('format', self.get_format_string())
 
996
            utf8_files += [('format', self.get_format_string())]
904
997
        try:
905
 
            for file, content in utf8_files:
906
 
                control_files.put_utf8(file, content)
 
998
            for (filename, content) in utf8_files:
 
999
                branch_transport.put_bytes(
 
1000
                    filename, content,
 
1001
                    mode=a_bzrdir._get_file_mode())
907
1002
        finally:
908
1003
            control_files.unlock()
909
1004
        return self.open(a_bzrdir, _found=True)
937
1032
    def set_default_format(klass, format):
938
1033
        klass._default_format = format
939
1034
 
 
1035
    def supports_stacking(self):
 
1036
        """True if this format records a stacked-on branch."""
 
1037
        return False
 
1038
 
940
1039
    @classmethod
941
1040
    def unregister_format(klass, format):
942
 
        assert klass._formats[format.get_format_string()] is format
943
1041
        del klass._formats[format.get_format_string()]
944
1042
 
945
1043
    def __str__(self):
949
1047
        """True if this format supports tags stored in the branch"""
950
1048
        return False  # by default
951
1049
 
952
 
    # XXX: Probably doesn't really belong here -- mbp 20070212
953
 
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
954
 
            lock_class):
955
 
        branch_transport = a_bzrdir.get_branch_transport(self)
956
 
        control_files = lockable_files.LockableFiles(branch_transport,
957
 
            lock_filename, lock_class)
958
 
        control_files.create_lock()
959
 
        control_files.lock_write()
960
 
        try:
961
 
            for filename, content in utf8_files:
962
 
                control_files.put_utf8(filename, content)
963
 
        finally:
964
 
            control_files.unlock()
965
 
 
966
1050
 
967
1051
class BranchHooks(Hooks):
968
1052
    """A dictionary mapping hook name to a list of callables for branch hooks.
1025
1109
        # local is the local branch or None, master is the target branch,
1026
1110
        # and an empty branch recieves new_revno of 0, new_revid of None.
1027
1111
        self['post_uncommit'] = []
 
1112
        # Introduced in 1.6
 
1113
        # Invoked before the tip of a branch changes.
 
1114
        # the api signature is
 
1115
        # (params) where params is a ChangeBranchTipParams with the members
 
1116
        # (branch, old_revno, new_revno, old_revid, new_revid)
 
1117
        self['pre_change_branch_tip'] = []
 
1118
        # Introduced in 1.4
 
1119
        # Invoked after the tip of a branch changes.
 
1120
        # the api signature is
 
1121
        # (params) where params is a ChangeBranchTipParams with the members
 
1122
        # (branch, old_revno, new_revno, old_revid, new_revid)
 
1123
        self['post_change_branch_tip'] = []
1028
1124
 
1029
1125
 
1030
1126
# install the default hooks into the Branch class.
1031
1127
Branch.hooks = BranchHooks()
1032
1128
 
1033
1129
 
 
1130
class ChangeBranchTipParams(object):
 
1131
    """Object holding parameters passed to *_change_branch_tip hooks.
 
1132
 
 
1133
    There are 5 fields that hooks may wish to access:
 
1134
 
 
1135
    :ivar branch: the branch being changed
 
1136
    :ivar old_revno: revision number before the change
 
1137
    :ivar new_revno: revision number after the change
 
1138
    :ivar old_revid: revision id before the change
 
1139
    :ivar new_revid: revision id after the change
 
1140
 
 
1141
    The revid fields are strings. The revno fields are integers.
 
1142
    """
 
1143
 
 
1144
    def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
 
1145
        """Create a group of ChangeBranchTip parameters.
 
1146
 
 
1147
        :param branch: The branch being changed.
 
1148
        :param old_revno: Revision number before the change.
 
1149
        :param new_revno: Revision number after the change.
 
1150
        :param old_revid: Tip revision id before the change.
 
1151
        :param new_revid: Tip revision id after the change.
 
1152
        """
 
1153
        self.branch = branch
 
1154
        self.old_revno = old_revno
 
1155
        self.new_revno = new_revno
 
1156
        self.old_revid = old_revid
 
1157
        self.new_revid = new_revid
 
1158
 
 
1159
    def __eq__(self, other):
 
1160
        return self.__dict__ == other.__dict__
 
1161
    
 
1162
    def __repr__(self):
 
1163
        return "<%s of %s from (%s, %s) to (%s, %s)>" % (
 
1164
            self.__class__.__name__, self.branch, 
 
1165
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
 
1166
 
 
1167
 
1034
1168
class BzrBranchFormat4(BranchFormat):
1035
1169
    """Bzr branch format 4.
1036
1170
 
1073
1207
        return "Bazaar-NG branch format 4"
1074
1208
 
1075
1209
 
1076
 
class BzrBranchFormat5(BranchFormat):
 
1210
class BranchFormatMetadir(BranchFormat):
 
1211
    """Common logic for meta-dir based branch formats."""
 
1212
 
 
1213
    def _branch_class(self):
 
1214
        """What class to instantiate on open calls."""
 
1215
        raise NotImplementedError(self._branch_class)
 
1216
 
 
1217
    def open(self, a_bzrdir, _found=False):
 
1218
        """Return the branch object for a_bzrdir.
 
1219
 
 
1220
        _found is a private parameter, do not use it. It is used to indicate
 
1221
               if format probing has already be done.
 
1222
        """
 
1223
        if not _found:
 
1224
            format = BranchFormat.find_format(a_bzrdir)
 
1225
            if format.__class__ != self.__class__:
 
1226
                raise AssertionError("wrong format %r found for %r" %
 
1227
                    (format, self))
 
1228
        try:
 
1229
            transport = a_bzrdir.get_branch_transport(None)
 
1230
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
1231
                                                         lockdir.LockDir)
 
1232
            return self._branch_class()(_format=self,
 
1233
                              _control_files=control_files,
 
1234
                              a_bzrdir=a_bzrdir,
 
1235
                              _repository=a_bzrdir.find_repository())
 
1236
        except errors.NoSuchFile:
 
1237
            raise errors.NotBranchError(path=transport.base)
 
1238
 
 
1239
    def __init__(self):
 
1240
        super(BranchFormatMetadir, self).__init__()
 
1241
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1242
 
 
1243
    def supports_tags(self):
 
1244
        return True
 
1245
 
 
1246
 
 
1247
class BzrBranchFormat5(BranchFormatMetadir):
1077
1248
    """Bzr branch format 5.
1078
1249
 
1079
1250
    This format has:
1086
1257
    This format is new in bzr 0.8.
1087
1258
    """
1088
1259
 
 
1260
    def _branch_class(self):
 
1261
        return BzrBranch5
 
1262
 
1089
1263
    def get_format_string(self):
1090
1264
        """See BranchFormat.get_format_string()."""
1091
1265
        return "Bazaar-NG branch format 5\n"
1101
1275
                      ]
1102
1276
        return self._initialize_helper(a_bzrdir, utf8_files)
1103
1277
 
1104
 
    def __init__(self):
1105
 
        super(BzrBranchFormat5, self).__init__()
1106
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1107
 
 
1108
 
    def open(self, a_bzrdir, _found=False):
1109
 
        """Return the branch object for a_bzrdir
1110
 
 
1111
 
        _found is a private parameter, do not use it. It is used to indicate
1112
 
               if format probing has already be done.
1113
 
        """
1114
 
        if not _found:
1115
 
            format = BranchFormat.find_format(a_bzrdir)
1116
 
            assert format.__class__ == self.__class__
1117
 
        try:
1118
 
            transport = a_bzrdir.get_branch_transport(None)
1119
 
            control_files = lockable_files.LockableFiles(transport, 'lock',
1120
 
                                                         lockdir.LockDir)
1121
 
            return BzrBranch5(_format=self,
1122
 
                              _control_files=control_files,
1123
 
                              a_bzrdir=a_bzrdir,
1124
 
                              _repository=a_bzrdir.find_repository())
1125
 
        except errors.NoSuchFile:
1126
 
            raise errors.NotBranchError(path=transport.base)
1127
 
 
1128
 
 
1129
 
class BzrBranchFormat6(BzrBranchFormat5):
 
1278
    def supports_tags(self):
 
1279
        return False
 
1280
 
 
1281
 
 
1282
class BzrBranchFormat6(BranchFormatMetadir):
1130
1283
    """Branch format with last-revision and tags.
1131
1284
 
1132
1285
    Unlike previous formats, this has no explicit revision history. Instead,
1137
1290
    and became the default in 0.91.
1138
1291
    """
1139
1292
 
 
1293
    def _branch_class(self):
 
1294
        return BzrBranch6
 
1295
 
1140
1296
    def get_format_string(self):
1141
1297
        """See BranchFormat.get_format_string()."""
1142
1298
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1153
1309
                      ]
1154
1310
        return self._initialize_helper(a_bzrdir, utf8_files)
1155
1311
 
1156
 
    def open(self, a_bzrdir, _found=False):
1157
 
        """Return the branch object for a_bzrdir
1158
 
 
1159
 
        _found is a private parameter, do not use it. It is used to indicate
1160
 
               if format probing has already be done.
1161
 
        """
1162
 
        if not _found:
1163
 
            format = BranchFormat.find_format(a_bzrdir)
1164
 
            assert format.__class__ == self.__class__
1165
 
        transport = a_bzrdir.get_branch_transport(None)
1166
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1167
 
                                                     lockdir.LockDir)
1168
 
        return BzrBranch6(_format=self,
1169
 
                          _control_files=control_files,
1170
 
                          a_bzrdir=a_bzrdir,
1171
 
                          _repository=a_bzrdir.find_repository())
1172
 
 
1173
 
    def supports_tags(self):
 
1312
 
 
1313
class BzrBranchFormat7(BranchFormatMetadir):
 
1314
    """Branch format with last-revision, tags, and a stacked location pointer.
 
1315
 
 
1316
    The stacked location pointer is passed down to the repository and requires
 
1317
    a repository format with supports_external_lookups = True.
 
1318
 
 
1319
    This format was introduced in bzr 1.6.
 
1320
    """
 
1321
 
 
1322
    def _branch_class(self):
 
1323
        return BzrBranch7
 
1324
 
 
1325
    def get_format_string(self):
 
1326
        """See BranchFormat.get_format_string()."""
 
1327
        return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
 
1328
 
 
1329
    def get_format_description(self):
 
1330
        """See BranchFormat.get_format_description()."""
 
1331
        return "Branch format 7"
 
1332
 
 
1333
    def initialize(self, a_bzrdir):
 
1334
        """Create a branch of this format in a_bzrdir."""
 
1335
        utf8_files = [('last-revision', '0 null:\n'),
 
1336
                      ('branch.conf', ''),
 
1337
                      ('tags', ''),
 
1338
                      ]
 
1339
        return self._initialize_helper(a_bzrdir, utf8_files)
 
1340
 
 
1341
    def __init__(self):
 
1342
        super(BzrBranchFormat7, self).__init__()
 
1343
        self._matchingbzrdir.repository_format = \
 
1344
            RepositoryFormatPackDevelopment1Subtree()
 
1345
 
 
1346
    def supports_stacking(self):
1174
1347
        return True
1175
1348
 
1176
1349
 
1241
1414
        """
1242
1415
        if not _found:
1243
1416
            format = BranchFormat.find_format(a_bzrdir)
1244
 
            assert format.__class__ == self.__class__
 
1417
            if format.__class__ != self.__class__:
 
1418
                raise AssertionError("wrong format %r found for %r" %
 
1419
                    (format, self))
1245
1420
        if location is None:
1246
1421
            location = self.get_reference(a_bzrdir)
1247
1422
        real_bzrdir = bzrdir.BzrDir.open(
1263
1438
# and not independently creatable, so are not registered.
1264
1439
__format5 = BzrBranchFormat5()
1265
1440
__format6 = BzrBranchFormat6()
 
1441
__format7 = BzrBranchFormat7()
1266
1442
BranchFormat.register_format(__format5)
1267
1443
BranchFormat.register_format(BranchReferenceFormat())
1268
1444
BranchFormat.register_format(__format6)
 
1445
BranchFormat.register_format(__format7)
1269
1446
BranchFormat.set_default_format(__format6)
1270
1447
_legacy_formats = [BzrBranchFormat4(),
1271
1448
                   ]
1276
1453
    Note that it's "local" in the context of the filesystem; it doesn't
1277
1454
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1278
1455
    it's writable, and can be accessed via the normal filesystem API.
 
1456
 
 
1457
    :ivar _transport: Transport for file operations on this branch's 
 
1458
        control files, typically pointing to the .bzr/branch directory.
 
1459
    :ivar repository: Repository for this branch.
 
1460
    :ivar base: The url of the base directory for this branch; the one 
 
1461
        containing the .bzr directory.
1279
1462
    """
1280
1463
    
1281
1464
    def __init__(self, _format=None,
1282
1465
                 _control_files=None, a_bzrdir=None, _repository=None):
1283
1466
        """Create new branch object at a particular location."""
1284
 
        Branch.__init__(self)
1285
1467
        if a_bzrdir is None:
1286
1468
            raise ValueError('a_bzrdir must be supplied')
1287
1469
        else:
1288
1470
            self.bzrdir = a_bzrdir
1289
 
        # self._transport used to point to the directory containing the
1290
 
        # control directory, but was not used - now it's just the transport
1291
 
        # for the branch control files.  mbp 20070212
1292
1471
        self._base = self.bzrdir.transport.clone('..').base
 
1472
        # XXX: We should be able to just do
 
1473
        #   self.base = self.bzrdir.root_transport.base
 
1474
        # but this does not quite work yet -- mbp 20080522
1293
1475
        self._format = _format
1294
1476
        if _control_files is None:
1295
1477
            raise ValueError('BzrBranch _control_files is None')
1296
1478
        self.control_files = _control_files
1297
1479
        self._transport = _control_files._transport
1298
1480
        self.repository = _repository
 
1481
        Branch.__init__(self)
1299
1482
 
1300
1483
    def __str__(self):
1301
1484
        return '%s(%r)' % (self.__class__.__name__, self.base)
1308
1491
 
1309
1492
    base = property(_get_base, doc="The URL for the root of this branch.")
1310
1493
 
1311
 
    def abspath(self, name):
1312
 
        """See Branch.abspath."""
1313
 
        return self.control_files._transport.abspath(name)
1314
 
 
1315
 
 
1316
 
    @deprecated_method(zero_sixteen)
1317
 
    @needs_read_lock
1318
 
    def get_root_id(self):
1319
 
        """See Branch.get_root_id."""
1320
 
        tree = self.repository.revision_tree(self.last_revision())
1321
 
        return tree.get_root_id()
1322
 
 
1323
1494
    def is_locked(self):
1324
1495
        return self.control_files.is_locked()
1325
1496
 
1369
1540
 
1370
1541
        This performs the actual writing to disk.
1371
1542
        It is intended to be called by BzrBranch5.set_revision_history."""
1372
 
        self.control_files.put_bytes(
1373
 
            'revision-history', '\n'.join(history))
 
1543
        self._transport.put_bytes(
 
1544
            'revision-history', '\n'.join(history),
 
1545
            mode=self.bzrdir._get_file_mode())
1374
1546
 
1375
1547
    @needs_write_lock
1376
1548
    def set_revision_history(self, rev_history):
1377
1549
        """See Branch.set_revision_history."""
1378
1550
        if 'evil' in debug.debug_flags:
1379
1551
            mutter_callsite(3, "set_revision_history scales with history.")
 
1552
        check_not_reserved_id = _mod_revision.check_not_reserved_id
 
1553
        for rev_id in rev_history:
 
1554
            check_not_reserved_id(rev_id)
 
1555
        if Branch.hooks['post_change_branch_tip']:
 
1556
            # Don't calculate the last_revision_info() if there are no hooks
 
1557
            # that will use it.
 
1558
            old_revno, old_revid = self.last_revision_info()
 
1559
        if len(rev_history) == 0:
 
1560
            revid = _mod_revision.NULL_REVISION
 
1561
        else:
 
1562
            revid = rev_history[-1]
 
1563
        self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
1380
1564
        self._write_revision_history(rev_history)
1381
1565
        self._clear_cached_state()
1382
1566
        self._cache_revision_history(rev_history)
1383
1567
        for hook in Branch.hooks['set_rh']:
1384
1568
            hook(self, rev_history)
 
1569
        if Branch.hooks['post_change_branch_tip']:
 
1570
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1385
1571
 
 
1572
    def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
 
1573
        """Run the pre_change_branch_tip hooks."""
 
1574
        hooks = Branch.hooks['pre_change_branch_tip']
 
1575
        if not hooks:
 
1576
            return
 
1577
        old_revno, old_revid = self.last_revision_info()
 
1578
        params = ChangeBranchTipParams(
 
1579
            self, old_revno, new_revno, old_revid, new_revid)
 
1580
        for hook in hooks:
 
1581
            try:
 
1582
                hook(params)
 
1583
            except errors.TipChangeRejected:
 
1584
                raise
 
1585
            except Exception:
 
1586
                exc_info = sys.exc_info()
 
1587
                hook_name = Branch.hooks.get_hook_name(hook)
 
1588
                raise errors.HookFailed(
 
1589
                    'pre_change_branch_tip', hook_name, exc_info)
 
1590
 
 
1591
    def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
 
1592
        """Run the post_change_branch_tip hooks."""
 
1593
        hooks = Branch.hooks['post_change_branch_tip']
 
1594
        if not hooks:
 
1595
            return
 
1596
        new_revno, new_revid = self.last_revision_info()
 
1597
        params = ChangeBranchTipParams(
 
1598
            self, old_revno, new_revno, old_revid, new_revid)
 
1599
        for hook in hooks:
 
1600
            hook(params)
 
1601
 
1386
1602
    @needs_write_lock
1387
1603
    def set_last_revision_info(self, revno, revision_id):
1388
1604
        """Set the last revision of this branch.
1396
1612
        be permitted.
1397
1613
        """
1398
1614
        revision_id = _mod_revision.ensure_null(revision_id)
 
1615
        # this old format stores the full history, but this api doesn't
 
1616
        # provide it, so we must generate, and might as well check it's
 
1617
        # correct
1399
1618
        history = self._lefthand_history(revision_id)
1400
 
        assert len(history) == revno, '%d != %d' % (len(history), revno)
 
1619
        if len(history) != revno:
 
1620
            raise AssertionError('%d != %d' % (len(history), revno))
1401
1621
        self.set_revision_history(history)
1402
1622
 
1403
1623
    def _gen_revision_history(self):
1404
 
        history = self.control_files.get('revision-history').read().split('\n')
 
1624
        history = self._transport.get_bytes('revision-history').split('\n')
1405
1625
        if history[-1:] == ['']:
1406
1626
            # There shouldn't be a trailing newline, but just in case.
1407
1627
            history.pop()
1423
1643
            raise errors.NoSuchRevision(self, revision_id)
1424
1644
        current_rev_id = revision_id
1425
1645
        new_history = []
 
1646
        check_not_reserved_id = _mod_revision.check_not_reserved_id
1426
1647
        # Do not include ghosts or graph origin in revision_history
1427
1648
        while (current_rev_id in parents_map and
1428
1649
               len(parents_map[current_rev_id]) > 0):
 
1650
            check_not_reserved_id(current_rev_id)
1429
1651
            new_history.append(current_rev_id)
1430
1652
            current_rev_id = parents_map[current_rev_id][0]
1431
1653
            parents_map = graph.get_parent_map([current_rev_id])
1446
1668
        self.set_revision_history(self._lefthand_history(revision_id,
1447
1669
            last_rev, other_branch))
1448
1670
 
1449
 
    @needs_write_lock
1450
 
    def update_revisions(self, other, stop_revision=None, overwrite=False):
1451
 
        """See Branch.update_revisions."""
1452
 
        other.lock_read()
1453
 
        try:
1454
 
            other_last_revno, other_last_revision = other.last_revision_info()
1455
 
            if stop_revision is None:
1456
 
                stop_revision = other_last_revision
1457
 
                if _mod_revision.is_null(stop_revision):
1458
 
                    # if there are no commits, we're done.
1459
 
                    return
1460
 
            # whats the current last revision, before we fetch [and change it
1461
 
            # possibly]
1462
 
            last_rev = _mod_revision.ensure_null(self.last_revision())
1463
 
            # we fetch here so that we don't process data twice in the common
1464
 
            # case of having something to pull, and so that the check for 
1465
 
            # already merged can operate on the just fetched graph, which will
1466
 
            # be cached in memory.
1467
 
            self.fetch(other, stop_revision)
1468
 
            # Check to see if one is an ancestor of the other
1469
 
            if not overwrite:
1470
 
                heads = self.repository.get_graph().heads([stop_revision,
1471
 
                                                           last_rev])
1472
 
                if heads == set([last_rev]):
1473
 
                    # The current revision is a decendent of the target,
1474
 
                    # nothing to do
1475
 
                    return
1476
 
                elif heads == set([stop_revision, last_rev]):
1477
 
                    # These branches have diverged
1478
 
                    raise errors.DivergedBranches(self, other)
1479
 
                assert heads == set([stop_revision])
1480
 
            if other_last_revision == stop_revision:
1481
 
                self.set_last_revision_info(other_last_revno,
1482
 
                                            other_last_revision)
1483
 
            else:
1484
 
                # TODO: jam 2007-11-29 Is there a way to determine the
1485
 
                #       revno without searching all of history??
1486
 
                if overwrite:
1487
 
                    self.generate_revision_history(stop_revision)
1488
 
                else:
1489
 
                    self.generate_revision_history(stop_revision,
1490
 
                        last_rev=last_rev, other_branch=other)
1491
 
        finally:
1492
 
            other.unlock()
1493
 
 
1494
1671
    def basis_tree(self):
1495
1672
        """See Branch.basis_tree."""
1496
1673
        return self.repository.revision_tree(self.last_revision())
1497
1674
 
1498
1675
    @needs_write_lock
1499
1676
    def pull(self, source, overwrite=False, stop_revision=None,
1500
 
             _hook_master=None, run_hooks=True, possible_transports=None):
 
1677
             _hook_master=None, run_hooks=True, possible_transports=None,
 
1678
             _override_hook_target=None):
1501
1679
        """See Branch.pull.
1502
1680
 
1503
1681
        :param _hook_master: Private parameter - set the branch to 
1504
 
            be supplied as the master to push hooks.
 
1682
            be supplied as the master to pull hooks.
1505
1683
        :param run_hooks: Private parameter - if false, this branch
1506
1684
            is being called because it's the master of the primary branch,
1507
1685
            so it should not run its hooks.
 
1686
        :param _override_hook_target: Private parameter - set the branch to be
 
1687
            supplied as the target_branch to pull hooks.
1508
1688
        """
1509
1689
        result = PullResult()
1510
1690
        result.source_branch = source
1511
 
        result.target_branch = self
 
1691
        if _override_hook_target is None:
 
1692
            result.target_branch = self
 
1693
        else:
 
1694
            result.target_branch = _override_hook_target
1512
1695
        source.lock_read()
1513
1696
        try:
 
1697
            # We assume that during 'pull' the local repository is closer than
 
1698
            # the remote one.
 
1699
            graph = self.repository.get_graph(source.repository)
1514
1700
            result.old_revno, result.old_revid = self.last_revision_info()
1515
 
            self.update_revisions(source, stop_revision, overwrite=overwrite)
 
1701
            self.update_revisions(source, stop_revision, overwrite=overwrite,
 
1702
                                  graph=graph)
1516
1703
            result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1517
1704
            result.new_revno, result.new_revid = self.last_revision_info()
1518
1705
            if _hook_master:
1519
1706
                result.master_branch = _hook_master
1520
 
                result.local_branch = self
 
1707
                result.local_branch = result.target_branch
1521
1708
            else:
1522
 
                result.master_branch = self
 
1709
                result.master_branch = result.target_branch
1523
1710
                result.local_branch = None
1524
1711
            if run_hooks:
1525
1712
                for hook in Branch.hooks['post_pull']:
1532
1719
        _locs = ['parent', 'pull', 'x-pull']
1533
1720
        for l in _locs:
1534
1721
            try:
1535
 
                return self.control_files.get(l).read().strip('\n')
 
1722
                return self._transport.get_bytes(l).strip('\n')
1536
1723
            except errors.NoSuchFile:
1537
1724
                pass
1538
1725
        return None
1615
1802
        result.source_branch = self
1616
1803
        result.target_branch = target
1617
1804
        result.old_revno, result.old_revid = target.last_revision_info()
1618
 
        try:
1619
 
            target.update_revisions(self, stop_revision)
1620
 
        except errors.DivergedBranches:
1621
 
            if not overwrite:
1622
 
                raise
1623
 
        if overwrite:
1624
 
            target.set_revision_history(self.revision_history())
 
1805
 
 
1806
        # We assume that during 'push' this repository is closer than
 
1807
        # the target.
 
1808
        graph = self.repository.get_graph(target.repository)
 
1809
        target.update_revisions(self, stop_revision, overwrite=overwrite,
 
1810
                                graph=graph)
1625
1811
        result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1626
1812
        result.new_revno, result.new_revid = target.last_revision_info()
1627
1813
        return result
1628
1814
 
1629
1815
    def get_parent(self):
1630
1816
        """See Branch.get_parent."""
1631
 
 
1632
 
        assert self.base[-1] == '/'
1633
1817
        parent = self._get_parent_location()
1634
1818
        if parent is None:
1635
1819
            return parent
1642
1826
        except errors.InvalidURLJoin, e:
1643
1827
            raise errors.InaccessibleParent(parent, self.base)
1644
1828
 
 
1829
    def get_stacked_on_url(self):
 
1830
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
1831
 
1645
1832
    def set_push_location(self, location):
1646
1833
        """See Branch.set_push_location."""
1647
1834
        self.get_config().set_user_option(
1654
1841
        # TODO: Maybe delete old location files?
1655
1842
        # URLs should never be unicode, even on the local fs,
1656
1843
        # FIXUP this and get_parent in a future branch format bump:
1657
 
        # read and rewrite the file, and have the new format code read
1658
 
        # using .get not .get_utf8. RBC 20060125
 
1844
        # read and rewrite the file. RBC 20060125
1659
1845
        if url is not None:
1660
1846
            if isinstance(url, unicode):
1661
 
                try: 
 
1847
                try:
1662
1848
                    url = url.encode('ascii')
1663
1849
                except UnicodeEncodeError:
1664
1850
                    raise errors.InvalidURL(url,
1669
1855
 
1670
1856
    def _set_parent_location(self, url):
1671
1857
        if url is None:
1672
 
            self.control_files._transport.delete('parent')
 
1858
            self._transport.delete('parent')
1673
1859
        else:
1674
 
            assert isinstance(url, str)
1675
 
            self.control_files.put_bytes('parent', url + '\n')
 
1860
            self._transport.put_bytes('parent', url + '\n',
 
1861
                mode=self.bzrdir._get_file_mode())
 
1862
 
 
1863
    def set_stacked_on_url(self, url):
 
1864
        raise errors.UnstackableBranchFormat(self._format, self.base)
1676
1865
 
1677
1866
 
1678
1867
class BzrBranch5(BzrBranch):
1681
1870
    It has support for a master_branch which is the data for bound branches.
1682
1871
    """
1683
1872
 
1684
 
    def __init__(self,
1685
 
                 _format,
1686
 
                 _control_files,
1687
 
                 a_bzrdir,
1688
 
                 _repository):
1689
 
        super(BzrBranch5, self).__init__(_format=_format,
1690
 
                                         _control_files=_control_files,
1691
 
                                         a_bzrdir=a_bzrdir,
1692
 
                                         _repository=_repository)
1693
 
        
1694
1873
    @needs_write_lock
1695
1874
    def pull(self, source, overwrite=False, stop_revision=None,
1696
 
             run_hooks=True, possible_transports=None):
 
1875
             run_hooks=True, possible_transports=None,
 
1876
             _override_hook_target=None):
1697
1877
        """Pull from source into self, updating my master if any.
1698
1878
        
1699
1879
        :param run_hooks: Private parameter - if false, this branch
1713
1893
                    run_hooks=False)
1714
1894
            return super(BzrBranch5, self).pull(source, overwrite,
1715
1895
                stop_revision, _hook_master=master_branch,
1716
 
                run_hooks=run_hooks)
 
1896
                run_hooks=run_hooks,
 
1897
                _override_hook_target=_override_hook_target)
1717
1898
        finally:
1718
1899
            if master_branch:
1719
1900
                master_branch.unlock()
1720
1901
 
1721
1902
    def get_bound_location(self):
1722
1903
        try:
1723
 
            return self.control_files.get_utf8('bound').read()[:-1]
 
1904
            return self._transport.get_bytes('bound')[:-1]
1724
1905
        except errors.NoSuchFile:
1725
1906
            return None
1726
1907
 
1752
1933
        :param location: URL to the target branch
1753
1934
        """
1754
1935
        if location:
1755
 
            self.control_files.put_utf8('bound', location+'\n')
 
1936
            self._transport.put_bytes('bound', location+'\n',
 
1937
                mode=self.bzrdir._get_file_mode())
1756
1938
        else:
1757
1939
            try:
1758
 
                self.control_files._transport.delete('bound')
 
1940
                self._transport.delete('bound')
1759
1941
            except errors.NoSuchFile:
1760
1942
                return False
1761
1943
            return True
1809
1991
        return None
1810
1992
 
1811
1993
 
1812
 
class BzrBranch6(BzrBranch5):
 
1994
class BzrBranch7(BzrBranch5):
 
1995
    """A branch with support for a fallback repository."""
 
1996
 
 
1997
    def _get_fallback_repository(self, url):
 
1998
        """Get the repository we fallback to at url."""
 
1999
        url = urlutils.join(self.base, url)
 
2000
        return bzrdir.BzrDir.open(url).open_branch().repository
 
2001
 
 
2002
    def _activate_fallback_location(self, url):
 
2003
        """Activate the branch/repository from url as a fallback repository."""
 
2004
        self.repository.add_fallback_repository(
 
2005
            self._get_fallback_repository(url))
 
2006
 
 
2007
    def _open_hook(self):
 
2008
        try:
 
2009
            url = self.get_stacked_on_url()
 
2010
        except (errors.UnstackableRepositoryFormat, errors.NotStacked,
 
2011
            errors.UnstackableBranchFormat):
 
2012
            pass
 
2013
        else:
 
2014
            self._activate_fallback_location(url)
 
2015
 
 
2016
    def _check_stackable_repo(self):
 
2017
        if not self.repository._format.supports_external_lookups:
 
2018
            raise errors.UnstackableRepositoryFormat(self.repository._format,
 
2019
                self.repository.base)
1813
2020
 
1814
2021
    def __init__(self, *args, **kwargs):
1815
 
        super(BzrBranch6, self).__init__(*args, **kwargs)
 
2022
        super(BzrBranch7, self).__init__(*args, **kwargs)
1816
2023
        self._last_revision_info_cache = None
1817
2024
        self._partial_revision_history_cache = []
1818
2025
 
1819
2026
    def _clear_cached_state(self):
1820
 
        super(BzrBranch6, self)._clear_cached_state()
 
2027
        super(BzrBranch7, self)._clear_cached_state()
1821
2028
        self._last_revision_info_cache = None
1822
2029
        self._partial_revision_history_cache = []
1823
2030
 
1824
 
    @needs_read_lock
1825
 
    def last_revision_info(self):
1826
 
        """Return information about the last revision.
1827
 
 
1828
 
        :return: A tuple (revno, revision_id).
1829
 
        """
1830
 
        if self._last_revision_info_cache is None:
1831
 
            self._last_revision_info_cache = self._last_revision_info()
1832
 
        return self._last_revision_info_cache
1833
 
 
1834
2031
    def _last_revision_info(self):
1835
 
        revision_string = self.control_files.get('last-revision').read()
 
2032
        revision_string = self._transport.get_bytes('last-revision')
1836
2033
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1837
2034
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1838
2035
        revno = int(revno)
1847
2044
        Intended to be called by set_last_revision_info and
1848
2045
        _write_revision_history.
1849
2046
        """
1850
 
        assert revision_id is not None, "Use NULL_REVISION, not None"
 
2047
        revision_id = _mod_revision.ensure_null(revision_id)
1851
2048
        out_string = '%d %s\n' % (revno, revision_id)
1852
 
        self.control_files.put_bytes('last-revision', out_string)
 
2049
        self._transport.put_bytes('last-revision', out_string,
 
2050
            mode=self.bzrdir._get_file_mode())
1853
2051
 
1854
2052
    @needs_write_lock
1855
2053
    def set_last_revision_info(self, revno, revision_id):
1856
2054
        revision_id = _mod_revision.ensure_null(revision_id)
 
2055
        old_revno, old_revid = self.last_revision_info()
1857
2056
        if self._get_append_revisions_only():
1858
2057
            self._check_history_violation(revision_id)
 
2058
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
1859
2059
        self._write_last_revision_info(revno, revision_id)
1860
2060
        self._clear_cached_state()
1861
2061
        self._last_revision_info_cache = revno, revision_id
 
2062
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1862
2063
 
1863
2064
    def _check_history_violation(self, revision_id):
1864
2065
        last_revision = _mod_revision.ensure_null(self.last_revision())
1870
2071
    def _gen_revision_history(self):
1871
2072
        """Generate the revision history from last revision
1872
2073
        """
1873
 
        self._extend_partial_history()
 
2074
        last_revno, last_revision = self.last_revision_info()
 
2075
        self._extend_partial_history(stop_index=last_revno-1)
1874
2076
        return list(reversed(self._partial_revision_history_cache))
1875
2077
 
1876
2078
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
1894
2096
            iterator = repo.iter_reverse_revision_history(start_revision)
1895
2097
            #skip the last revision in the list
1896
2098
            next_revision = iterator.next()
1897
 
            assert next_revision == start_revision
1898
2099
        for revision_id in iterator:
1899
2100
            self._partial_revision_history_cache.append(revision_id)
1900
2101
            if (stop_index is not None and
1967
2168
        """See Branch.get_old_bound_location"""
1968
2169
        return self._get_bound_location(False)
1969
2170
 
 
2171
    def get_stacked_on_url(self):
 
2172
        self._check_stackable_repo()
 
2173
        stacked_url = self._get_config_location('stacked_on_location')
 
2174
        if stacked_url is None:
 
2175
            raise errors.NotStacked(self)
 
2176
        return stacked_url
 
2177
 
1970
2178
    def set_append_revisions_only(self, enabled):
1971
2179
        if enabled:
1972
2180
            value = 'True'
1975
2183
        self.get_config().set_user_option('append_revisions_only', value,
1976
2184
            warn_masked=True)
1977
2185
 
 
2186
    def set_stacked_on_url(self, url):
 
2187
        self._check_stackable_repo()
 
2188
        if not url:
 
2189
            try:
 
2190
                old_url = self.get_stacked_on_url()
 
2191
            except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2192
                errors.UnstackableRepositoryFormat):
 
2193
                return
 
2194
            url = ''
 
2195
            # repositories don't offer an interface to remove fallback
 
2196
            # repositories today; take the conceptually simpler option and just
 
2197
            # reopen it.
 
2198
            self.repository = self.bzrdir.find_repository()
 
2199
            # for every revision reference the branch has, ensure it is pulled
 
2200
            # in.
 
2201
            source_repository = self._get_fallback_repository(old_url)
 
2202
            for revision_id in chain([self.last_revision()],
 
2203
                self.tags.get_reverse_tag_dict()):
 
2204
                self.repository.fetch(source_repository, revision_id,
 
2205
                    find_ghosts=True)
 
2206
        else:
 
2207
            self._activate_fallback_location(url)
 
2208
        # write this out after the repository is stacked to avoid setting a
 
2209
        # stacked config that doesn't work.
 
2210
        self._set_config_location('stacked_on_location', url)
 
2211
 
1978
2212
    def _get_append_revisions_only(self):
1979
2213
        value = self.get_config().get_user_option('append_revisions_only')
1980
2214
        return value == 'True'
2030
2264
            raise errors.NoSuchRevision(self, revno)
2031
2265
 
2032
2266
        if history is not None:
2033
 
            assert len(history) == last_revno, 'revno/history mismatch'
2034
2267
            return history[revno - 1]
2035
2268
 
2036
2269
        index = last_revno - revno
2056
2289
        return self.revno() - index
2057
2290
 
2058
2291
 
 
2292
class BzrBranch6(BzrBranch7):
 
2293
    """See BzrBranchFormat6 for the capabilities of this branch.
 
2294
 
 
2295
    This subclass of BzrBranch7 disables the new features BzrBranch7 added,
 
2296
    i.e. stacking.
 
2297
    """
 
2298
 
 
2299
    def get_stacked_on_url(self):
 
2300
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2301
 
 
2302
    def set_stacked_on_url(self, url):
 
2303
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2304
 
 
2305
 
2059
2306
######################################################################
2060
2307
# results of operations
2061
2308
 
2078
2325
    :ivar old_revid: Tip revision id before pull.
2079
2326
    :ivar new_revid: Tip revision id after pull.
2080
2327
    :ivar source_branch: Source (local) branch object.
2081
 
    :ivar master_branch: Master branch of the target, or None.
 
2328
    :ivar master_branch: Master branch of the target, or the target if no
 
2329
        Master
 
2330
    :ivar local_branch: target branch if there is a Master, else None
2082
2331
    :ivar target_branch: Target/destination branch object.
 
2332
    :ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2083
2333
    """
2084
2334
 
2085
2335
    def __int__(self):
2149
2399
        new_branch = format.open(branch.bzrdir, _found=True)
2150
2400
 
2151
2401
        # Copy source data into target
2152
 
        new_branch.set_last_revision_info(*branch.last_revision_info())
 
2402
        new_branch._write_last_revision_info(*branch.last_revision_info())
2153
2403
        new_branch.set_parent(branch.get_parent())
2154
2404
        new_branch.set_bound_location(branch.get_bound_location())
2155
2405
        new_branch.set_push_location(branch.get_push_location())
2158
2408
        new_branch.tags._set_tag_dict({})
2159
2409
 
2160
2410
        # Copying done; now update target format
2161
 
        new_branch.control_files.put_utf8('format',
2162
 
            format.get_format_string())
 
2411
        new_branch._transport.put_bytes('format',
 
2412
            format.get_format_string(),
 
2413
            mode=new_branch.bzrdir._get_file_mode())
2163
2414
 
2164
2415
        # Clean up old files
2165
 
        new_branch.control_files._transport.delete('revision-history')
 
2416
        new_branch._transport.delete('revision-history')
2166
2417
        try:
2167
2418
            branch.set_parent(None)
2168
2419
        except errors.NoSuchFile:
2169
2420
            pass
2170
2421
        branch.set_bound_location(None)
 
2422
 
 
2423
 
 
2424
class Converter6to7(object):
 
2425
    """Perform an in-place upgrade of format 6 to format 7"""
 
2426
 
 
2427
    def convert(self, branch):
 
2428
        format = BzrBranchFormat7()
 
2429
        branch._set_config_location('stacked_on_location', '')
 
2430
        # update target format
 
2431
        branch._transport.put_bytes('format', format.get_format_string())