~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-12-15 21:00:20 UTC
  • mfrom: (3113.5.1 test.174055)
  • Revision ID: pqm@pqm.ubuntu.com-20071215210020-m28kk1qmbcc9n6qs
(bialix) XFAIL test for #174055: can't run bzr info while dirstate
 is locked

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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
 
18
from cStringIO import StringIO
19
19
 
20
20
from bzrlib.lazy_import import lazy_import
21
21
lazy_import(globals(), """
22
 
from itertools import chain
 
22
from warnings import warn
 
23
 
 
24
import bzrlib
23
25
from bzrlib import (
24
26
        bzrdir,
25
27
        cache_utf8,
28
30
        errors,
29
31
        lockdir,
30
32
        lockable_files,
31
 
        repository,
 
33
        osutils,
32
34
        revision as _mod_revision,
33
35
        transport,
 
36
        tree,
34
37
        tsort,
35
38
        ui,
36
39
        urlutils,
37
40
        )
38
 
from bzrlib.config import BranchConfig
39
 
from bzrlib.repofmt.pack_repo import RepositoryFormatPackDevelopment1Subtree
 
41
from bzrlib.config import BranchConfig, TreeConfig
 
42
from bzrlib.lockable_files import LockableFiles, TransportLock
40
43
from bzrlib.tag import (
41
44
    BasicTags,
42
45
    DisabledTags,
44
47
""")
45
48
 
46
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
50
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
 
51
                           HistoryMissing, InvalidRevisionId,
 
52
                           InvalidRevisionNumber, LockError, NoSuchFile,
 
53
                           NoSuchRevision, NotVersionedError,
 
54
                           NotBranchError, UninitializableFormat,
 
55
                           UnlistableStore, UnlistableBranch,
 
56
                           )
47
57
from bzrlib.hooks import Hooks
48
 
from bzrlib.symbol_versioning import (
49
 
    deprecated_in,
50
 
    deprecated_method,
51
 
    )
52
 
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
 
58
from bzrlib.symbol_versioning import (deprecated_function,
 
59
                                      deprecated_method,
 
60
                                      DEPRECATED_PARAMETER,
 
61
                                      deprecated_passed,
 
62
                                      zero_eight, zero_nine, zero_sixteen,
 
63
                                      zero_ninetyone,
 
64
                                      )
 
65
from bzrlib.trace import mutter, mutter_callsite, note
53
66
 
54
67
 
55
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
89
102
        self.tags = self._make_tags()
90
103
        self._revision_history_cache = None
91
104
        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."""
97
105
 
98
106
    def break_lock(self):
99
107
        """Break a lock if one is present from another instance.
203
211
        :return: A dictionary mapping revision_id => dotted revno.
204
212
        """
205
213
        last_revision = self.last_revision()
206
 
        revision_graph = repository._old_get_graph(self.repository,
207
 
            last_revision)
 
214
        revision_graph = self.repository.get_revision_graph(last_revision)
208
215
        merge_sorted_revisions = tsort.merge_sort(
209
216
            revision_graph,
210
217
            last_revision,
231
238
        """
232
239
        self.control_files.dont_leave_in_place()
233
240
 
234
 
    @deprecated_method(deprecated_in((0, 16, 0)))
235
241
    def abspath(self, name):
236
242
        """Return absolute filename for something in the branch
237
243
        
273
279
            if last_revision is None:
274
280
                pb.update('get source history')
275
281
                last_revision = from_branch.last_revision()
276
 
                last_revision = _mod_revision.ensure_null(last_revision)
 
282
                if last_revision is None:
 
283
                    last_revision = _mod_revision.NULL_REVISION
277
284
            return self.repository.fetch(from_branch.repository,
278
285
                                         revision_id=last_revision,
279
286
                                         pb=nested_pb)
328
335
        The delta is relative to its mainline predecessor, or the
329
336
        empty tree for revision 1.
330
337
        """
 
338
        assert isinstance(revno, int)
331
339
        rh = self.revision_history()
332
340
        if not (1 <= revno <= len(rh)):
333
 
            raise errors.InvalidRevisionNumber(revno)
 
341
            raise InvalidRevisionNumber(revno)
334
342
        return self.repository.get_revision_delta(rh[revno-1])
335
343
 
336
 
    def get_stacked_on_url(self):
337
 
        """Get the URL this branch is stacked against.
 
344
    @deprecated_method(zero_sixteen)
 
345
    def get_root_id(self):
 
346
        """Return the id of this branches root
338
347
 
339
 
        :raises NotStacked: If the branch is not stacked.
340
 
        :raises UnstackableBranchFormat: If the branch does not support
341
 
            stacking.
 
348
        Deprecated: branches don't have root ids-- trees do.
 
349
        Use basis_tree().get_root_id() instead.
342
350
        """
343
 
        raise NotImplementedError(self.get_stacked_on_url)
 
351
        raise NotImplementedError(self.get_root_id)
344
352
 
345
353
    def print_file(self, file, revision_id):
346
354
        """Print `file` to stdout."""
349
357
    def set_revision_history(self, rev_history):
350
358
        raise NotImplementedError(self.set_revision_history)
351
359
 
352
 
    def set_stacked_on_url(self, url):
353
 
        """Set the URL this branch is stacked against.
354
 
 
355
 
        :raises UnstackableBranchFormat: If the branch does not support
356
 
            stacking.
357
 
        :raises UnstackableRepositoryFormat: If the repository does not support
358
 
            stacking.
359
 
        """
360
 
        raise NotImplementedError(self.set_stacked_on_url)
361
 
 
362
360
    def _cache_revision_history(self, rev_history):
363
361
        """Set the cached revision history to rev_history.
364
362
 
389
387
        """
390
388
        self._revision_history_cache = None
391
389
        self._revision_id_to_revno_cache = None
392
 
        self._last_revision_info_cache = None
393
390
 
394
391
    def _gen_revision_history(self):
395
392
        """Return sequence of revision hashes on to this branch.
439
436
        raise errors.UpgradeRequired(self.base)
440
437
 
441
438
    def last_revision(self):
442
 
        """Return last revision id, or NULL_REVISION."""
443
 
        return self.last_revision_info()[1]
 
439
        """Return last revision id, or None"""
 
440
        ph = self.revision_history()
 
441
        if ph:
 
442
            return ph[-1]
 
443
        else:
 
444
            return _mod_revision.NULL_REVISION
444
445
 
445
 
    @needs_read_lock
446
446
    def last_revision_info(self):
447
447
        """Return information about the last revision.
448
448
 
449
 
        :return: A tuple (revno, revision_id).
 
449
        :return: A tuple (revno, last_revision_id).
450
450
        """
451
 
        if self._last_revision_info_cache is None:
452
 
            self._last_revision_info_cache = self._last_revision_info()
453
 
        return self._last_revision_info_cache
454
 
 
455
 
    def _last_revision_info(self):
456
451
        rh = self.revision_history()
457
452
        revno = len(rh)
458
453
        if revno:
460
455
        else:
461
456
            return (0, _mod_revision.NULL_REVISION)
462
457
 
463
 
    @deprecated_method(deprecated_in((1, 6, 0)))
464
458
    def missing_revisions(self, other, stop_revision=None):
465
459
        """Return a list of new revisions that would perfectly fit.
466
460
        
474
468
        common_index = min(self_len, other_len) -1
475
469
        if common_index >= 0 and \
476
470
            self_history[common_index] != other_history[common_index]:
477
 
            raise errors.DivergedBranches(self, other)
 
471
            raise DivergedBranches(self, other)
478
472
 
479
473
        if stop_revision is None:
480
474
            stop_revision = other_len
481
475
        else:
 
476
            assert isinstance(stop_revision, int)
482
477
            if stop_revision > other_len:
483
478
                raise errors.NoSuchRevision(self, stop_revision)
484
479
        return other_history[self_len:stop_revision]
485
480
 
486
 
    @needs_write_lock
487
 
    def update_revisions(self, other, stop_revision=None, overwrite=False,
488
 
                         graph=None):
 
481
    def update_revisions(self, other, stop_revision=None):
489
482
        """Pull in new perfect-fit revisions.
490
483
 
491
484
        :param other: Another Branch to pull from
492
485
        :param stop_revision: Updated until the given revision
493
 
        :param overwrite: Always set the branch pointer, rather than checking
494
 
            to see if it is a proper descendant.
495
 
        :param graph: A Graph object that can be used to query history
496
 
            information. This can be None.
497
486
        :return: None
498
487
        """
499
 
        other.lock_read()
500
 
        try:
501
 
            other_revno, other_last_revision = other.last_revision_info()
502
 
            stop_revno = None # unknown
503
 
            if stop_revision is None:
504
 
                stop_revision = other_last_revision
505
 
                if _mod_revision.is_null(stop_revision):
506
 
                    # if there are no commits, we're done.
507
 
                    return
508
 
                stop_revno = other_revno
509
 
 
510
 
            # what's the current last revision, before we fetch [and change it
511
 
            # possibly]
512
 
            last_rev = _mod_revision.ensure_null(self.last_revision())
513
 
            # we fetch here so that we don't process data twice in the common
514
 
            # case of having something to pull, and so that the check for 
515
 
            # already merged can operate on the just fetched graph, which will
516
 
            # be cached in memory.
517
 
            self.fetch(other, stop_revision)
518
 
            # Check to see if one is an ancestor of the other
519
 
            if not overwrite:
520
 
                if graph is None:
521
 
                    graph = self.repository.get_graph()
522
 
                if self._check_if_descendant_or_diverged(
523
 
                        stop_revision, last_rev, graph, other):
524
 
                    # stop_revision is a descendant of last_rev, but we aren't
525
 
                    # overwriting, so we're done.
526
 
                    return
527
 
            if stop_revno is None:
528
 
                if graph is None:
529
 
                    graph = self.repository.get_graph()
530
 
                this_revno, this_last_revision = self.last_revision_info()
531
 
                stop_revno = graph.find_distance_to_null(stop_revision,
532
 
                                [(other_last_revision, other_revno),
533
 
                                 (this_last_revision, this_revno)])
534
 
            self.set_last_revision_info(stop_revno, stop_revision)
535
 
        finally:
536
 
            other.unlock()
 
488
        raise NotImplementedError(self.update_revisions)
537
489
 
538
490
    def revision_id_to_revno(self, revision_id):
539
491
        """Given a revision id, return its revno"""
556
508
        return history[revno - 1]
557
509
 
558
510
    def pull(self, source, overwrite=False, stop_revision=None,
559
 
             possible_transports=None, _override_hook_target=None):
 
511
             possible_transports=None):
560
512
        """Mirror source into this branch.
561
513
 
562
514
        This branch is considered to be 'local', having low latency.
695
647
        Zero (the NULL revision) is considered invalid
696
648
        """
697
649
        if revno < 1 or revno > self.revno():
698
 
            raise errors.InvalidRevisionNumber(revno)
 
650
            raise InvalidRevisionNumber(revno)
699
651
 
700
652
    @needs_read_lock
701
653
    def clone(self, to_bzrdir, revision_id=None):
774
726
        :return: A BranchCheckResult.
775
727
        """
776
728
        mainline_parent_id = None
777
 
        last_revno, last_revision_id = self.last_revision_info()
778
 
        real_rev_history = list(self.repository.iter_reverse_revision_history(
779
 
                                last_revision_id))
780
 
        real_rev_history.reverse()
781
 
        if len(real_rev_history) != last_revno:
782
 
            raise errors.BzrCheckError('revno does not match len(mainline)'
783
 
                ' %s != %s' % (last_revno, len(real_rev_history)))
784
 
        # TODO: We should probably also check that real_rev_history actually
785
 
        #       matches self.revision_history()
786
 
        for revision_id in real_rev_history:
 
729
        for revision_id in self.revision_history():
787
730
            try:
788
731
                revision = self.repository.get_revision(revision_id)
789
732
            except errors.NoSuchRevision, e:
814
757
        return format
815
758
 
816
759
    def create_checkout(self, to_location, revision_id=None,
817
 
                        lightweight=False, accelerator_tree=None,
818
 
                        hardlink=False):
 
760
                        lightweight=False):
819
761
        """Create a checkout of a branch.
820
762
        
821
763
        :param to_location: The url to produce the checkout at
822
764
        :param revision_id: The revision to check out
823
765
        :param lightweight: If True, produce a lightweight checkout, otherwise,
824
766
        produce a bound branch (heavyweight checkout)
825
 
        :param accelerator_tree: A tree which can be used for retrieving file
826
 
            contents more quickly than the revision tree, i.e. a workingtree.
827
 
            The revision tree will be used for cases where accelerator_tree's
828
 
            content is different.
829
 
        :param hardlink: If true, hard-link files from accelerator_tree,
830
 
            where possible.
831
767
        :return: The tree of the created checkout
832
768
        """
833
769
        t = transport.get_transport(to_location)
847
783
            checkout_branch.pull(self, stop_revision=revision_id)
848
784
            from_branch=None
849
785
        tree = checkout.create_workingtree(revision_id,
850
 
                                           from_branch=from_branch,
851
 
                                           accelerator_tree=accelerator_tree,
852
 
                                           hardlink=hardlink)
 
786
                                           from_branch=from_branch)
853
787
        basis_tree = tree.basis_tree()
854
788
        basis_tree.lock_read()
855
789
        try:
862
796
            basis_tree.unlock()
863
797
        return tree
864
798
 
865
 
    @needs_write_lock
866
 
    def reconcile(self, thorough=True):
867
 
        """Make sure the data stored in this branch is consistent."""
868
 
        from bzrlib.reconcile import BranchReconciler
869
 
        reconciler = BranchReconciler(self, thorough=thorough)
870
 
        reconciler.reconcile()
871
 
        return reconciler
872
 
 
873
799
    def reference_parent(self, file_id, path):
874
800
        """Return the parent branch for a tree-reference file_id
875
801
        :param file_id: The file_id of the tree reference
882
808
    def supports_tags(self):
883
809
        return self._format.supports_tags()
884
810
 
885
 
    def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
886
 
                                         other_branch):
887
 
        """Ensure that revision_b is a descendant of revision_a.
888
 
 
889
 
        This is a helper function for update_revisions.
890
 
        
891
 
        :raises: DivergedBranches if revision_b has diverged from revision_a.
892
 
        :returns: True if revision_b is a descendant of revision_a.
893
 
        """
894
 
        relation = self._revision_relations(revision_a, revision_b, graph)
895
 
        if relation == 'b_descends_from_a':
896
 
            return True
897
 
        elif relation == 'diverged':
898
 
            raise errors.DivergedBranches(self, other_branch)
899
 
        elif relation == 'a_descends_from_b':
900
 
            return False
901
 
        else:
902
 
            raise AssertionError("invalid relation: %r" % (relation,))
903
 
 
904
 
    def _revision_relations(self, revision_a, revision_b, graph):
905
 
        """Determine the relationship between two revisions.
906
 
        
907
 
        :returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
908
 
        """
909
 
        heads = graph.heads([revision_a, revision_b])
910
 
        if heads == set([revision_b]):
911
 
            return 'b_descends_from_a'
912
 
        elif heads == set([revision_a, revision_b]):
913
 
            # These branches have diverged
914
 
            return 'diverged'
915
 
        elif heads == set([revision_a]):
916
 
            return 'a_descends_from_b'
917
 
        else:
918
 
            raise AssertionError("invalid heads: %r" % (heads,))
919
 
 
920
811
 
921
812
class BranchFormat(object):
922
813
    """An encapsulation of the initialization and open routines for a format.
955
846
            transport = a_bzrdir.get_branch_transport(None)
956
847
            format_string = transport.get("format").read()
957
848
            return klass._formats[format_string]
958
 
        except errors.NoSuchFile:
959
 
            raise errors.NotBranchError(path=transport.base)
 
849
        except NoSuchFile:
 
850
            raise NotBranchError(path=transport.base)
960
851
        except KeyError:
961
 
            raise errors.UnknownFormatError(format=format_string, kind='branch')
 
852
            raise errors.UnknownFormatError(format=format_string)
962
853
 
963
854
    @classmethod
964
855
    def get_default_format(klass):
1022
913
        control_files.create_lock()
1023
914
        control_files.lock_write()
1024
915
        if set_format:
1025
 
            utf8_files += [('format', self.get_format_string())]
 
916
            control_files.put_utf8('format', self.get_format_string())
1026
917
        try:
1027
 
            for (filename, content) in utf8_files:
1028
 
                branch_transport.put_bytes(
1029
 
                    filename, content,
1030
 
                    mode=a_bzrdir._get_file_mode())
 
918
            for file, content in utf8_files:
 
919
                control_files.put_utf8(file, content)
1031
920
        finally:
1032
921
            control_files.unlock()
1033
922
        return self.open(a_bzrdir, _found=True)
1061
950
    def set_default_format(klass, format):
1062
951
        klass._default_format = format
1063
952
 
1064
 
    def supports_stacking(self):
1065
 
        """True if this format records a stacked-on branch."""
1066
 
        return False
1067
 
 
1068
953
    @classmethod
1069
954
    def unregister_format(klass, format):
 
955
        assert klass._formats[format.get_format_string()] is format
1070
956
        del klass._formats[format.get_format_string()]
1071
957
 
1072
958
    def __str__(self):
1076
962
        """True if this format supports tags stored in the branch"""
1077
963
        return False  # by default
1078
964
 
 
965
    # XXX: Probably doesn't really belong here -- mbp 20070212
 
966
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
967
            lock_class):
 
968
        branch_transport = a_bzrdir.get_branch_transport(self)
 
969
        control_files = lockable_files.LockableFiles(branch_transport,
 
970
            lock_filename, lock_class)
 
971
        control_files.create_lock()
 
972
        control_files.lock_write()
 
973
        try:
 
974
            for filename, content in utf8_files:
 
975
                control_files.put_utf8(filename, content)
 
976
        finally:
 
977
            control_files.unlock()
 
978
 
1079
979
 
1080
980
class BranchHooks(Hooks):
1081
981
    """A dictionary mapping hook name to a list of callables for branch hooks.
1138
1038
        # local is the local branch or None, master is the target branch,
1139
1039
        # and an empty branch recieves new_revno of 0, new_revid of None.
1140
1040
        self['post_uncommit'] = []
1141
 
        # Introduced in 1.6
1142
 
        # Invoked before the tip of a branch changes.
1143
 
        # the api signature is
1144
 
        # (params) where params is a ChangeBranchTipParams with the members
1145
 
        # (branch, old_revno, new_revno, old_revid, new_revid)
1146
 
        self['pre_change_branch_tip'] = []
1147
 
        # Introduced in 1.4
1148
 
        # Invoked after the tip of a branch changes.
1149
 
        # the api signature is
1150
 
        # (params) where params is a ChangeBranchTipParams with the members
1151
 
        # (branch, old_revno, new_revno, old_revid, new_revid)
1152
 
        self['post_change_branch_tip'] = []
1153
1041
 
1154
1042
 
1155
1043
# install the default hooks into the Branch class.
1156
1044
Branch.hooks = BranchHooks()
1157
1045
 
1158
1046
 
1159
 
class ChangeBranchTipParams(object):
1160
 
    """Object holding parameters passed to *_change_branch_tip hooks.
1161
 
 
1162
 
    There are 5 fields that hooks may wish to access:
1163
 
 
1164
 
    :ivar branch: the branch being changed
1165
 
    :ivar old_revno: revision number before the change
1166
 
    :ivar new_revno: revision number after the change
1167
 
    :ivar old_revid: revision id before the change
1168
 
    :ivar new_revid: revision id after the change
1169
 
 
1170
 
    The revid fields are strings. The revno fields are integers.
1171
 
    """
1172
 
 
1173
 
    def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1174
 
        """Create a group of ChangeBranchTip parameters.
1175
 
 
1176
 
        :param branch: The branch being changed.
1177
 
        :param old_revno: Revision number before the change.
1178
 
        :param new_revno: Revision number after the change.
1179
 
        :param old_revid: Tip revision id before the change.
1180
 
        :param new_revid: Tip revision id after the change.
1181
 
        """
1182
 
        self.branch = branch
1183
 
        self.old_revno = old_revno
1184
 
        self.new_revno = new_revno
1185
 
        self.old_revid = old_revid
1186
 
        self.new_revid = new_revid
1187
 
 
1188
 
    def __eq__(self, other):
1189
 
        return self.__dict__ == other.__dict__
1190
 
    
1191
 
    def __repr__(self):
1192
 
        return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1193
 
            self.__class__.__name__, self.branch, 
1194
 
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1195
 
 
1196
 
 
1197
1047
class BzrBranchFormat4(BranchFormat):
1198
1048
    """Bzr branch format 4.
1199
1049
 
1236
1086
        return "Bazaar-NG branch format 4"
1237
1087
 
1238
1088
 
1239
 
class BranchFormatMetadir(BranchFormat):
1240
 
    """Common logic for meta-dir based branch formats."""
1241
 
 
1242
 
    def _branch_class(self):
1243
 
        """What class to instantiate on open calls."""
1244
 
        raise NotImplementedError(self._branch_class)
 
1089
class BzrBranchFormat5(BranchFormat):
 
1090
    """Bzr branch format 5.
 
1091
 
 
1092
    This format has:
 
1093
     - a revision-history file.
 
1094
     - a format string
 
1095
     - a lock dir guarding the branch itself
 
1096
     - all of this stored in a branch/ subdirectory
 
1097
     - works with shared repositories.
 
1098
 
 
1099
    This format is new in bzr 0.8.
 
1100
    """
 
1101
 
 
1102
    def get_format_string(self):
 
1103
        """See BranchFormat.get_format_string()."""
 
1104
        return "Bazaar-NG branch format 5\n"
 
1105
 
 
1106
    def get_format_description(self):
 
1107
        """See BranchFormat.get_format_description()."""
 
1108
        return "Branch format 5"
 
1109
        
 
1110
    def initialize(self, a_bzrdir):
 
1111
        """Create a branch of this format in a_bzrdir."""
 
1112
        utf8_files = [('revision-history', ''),
 
1113
                      ('branch-name', ''),
 
1114
                      ]
 
1115
        return self._initialize_helper(a_bzrdir, utf8_files)
 
1116
 
 
1117
    def __init__(self):
 
1118
        super(BzrBranchFormat5, self).__init__()
 
1119
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1245
1120
 
1246
1121
    def open(self, a_bzrdir, _found=False):
1247
 
        """Return the branch object for a_bzrdir.
 
1122
        """Return the branch object for a_bzrdir
1248
1123
 
1249
1124
        _found is a private parameter, do not use it. It is used to indicate
1250
1125
               if format probing has already be done.
1251
1126
        """
1252
1127
        if not _found:
1253
1128
            format = BranchFormat.find_format(a_bzrdir)
1254
 
            if format.__class__ != self.__class__:
1255
 
                raise AssertionError("wrong format %r found for %r" %
1256
 
                    (format, self))
 
1129
            assert format.__class__ == self.__class__
1257
1130
        try:
1258
1131
            transport = a_bzrdir.get_branch_transport(None)
1259
1132
            control_files = lockable_files.LockableFiles(transport, 'lock',
1260
1133
                                                         lockdir.LockDir)
1261
 
            return self._branch_class()(_format=self,
 
1134
            return BzrBranch5(_format=self,
1262
1135
                              _control_files=control_files,
1263
1136
                              a_bzrdir=a_bzrdir,
1264
1137
                              _repository=a_bzrdir.find_repository())
1265
 
        except errors.NoSuchFile:
1266
 
            raise errors.NotBranchError(path=transport.base)
1267
 
 
1268
 
    def __init__(self):
1269
 
        super(BranchFormatMetadir, self).__init__()
1270
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1271
 
 
1272
 
    def supports_tags(self):
1273
 
        return True
1274
 
 
1275
 
 
1276
 
class BzrBranchFormat5(BranchFormatMetadir):
1277
 
    """Bzr branch format 5.
1278
 
 
1279
 
    This format has:
1280
 
     - a revision-history file.
1281
 
     - a format string
1282
 
     - a lock dir guarding the branch itself
1283
 
     - all of this stored in a branch/ subdirectory
1284
 
     - works with shared repositories.
1285
 
 
1286
 
    This format is new in bzr 0.8.
1287
 
    """
1288
 
 
1289
 
    def _branch_class(self):
1290
 
        return BzrBranch5
1291
 
 
1292
 
    def get_format_string(self):
1293
 
        """See BranchFormat.get_format_string()."""
1294
 
        return "Bazaar-NG branch format 5\n"
1295
 
 
1296
 
    def get_format_description(self):
1297
 
        """See BranchFormat.get_format_description()."""
1298
 
        return "Branch format 5"
1299
 
        
1300
 
    def initialize(self, a_bzrdir):
1301
 
        """Create a branch of this format in a_bzrdir."""
1302
 
        utf8_files = [('revision-history', ''),
1303
 
                      ('branch-name', ''),
1304
 
                      ]
1305
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1306
 
 
1307
 
    def supports_tags(self):
1308
 
        return False
1309
 
 
1310
 
 
1311
 
class BzrBranchFormat6(BranchFormatMetadir):
 
1138
        except NoSuchFile:
 
1139
            raise NotBranchError(path=transport.base)
 
1140
 
 
1141
 
 
1142
class BzrBranchFormat6(BzrBranchFormat5):
1312
1143
    """Branch format with last-revision and tags.
1313
1144
 
1314
1145
    Unlike previous formats, this has no explicit revision history. Instead,
1319
1150
    and became the default in 0.91.
1320
1151
    """
1321
1152
 
1322
 
    def _branch_class(self):
1323
 
        return BzrBranch6
1324
 
 
1325
1153
    def get_format_string(self):
1326
1154
        """See BranchFormat.get_format_string()."""
1327
1155
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1338
1166
                      ]
1339
1167
        return self._initialize_helper(a_bzrdir, utf8_files)
1340
1168
 
1341
 
 
1342
 
class BzrBranchFormat7(BranchFormatMetadir):
1343
 
    """Branch format with last-revision, tags, and a stacked location pointer.
1344
 
 
1345
 
    The stacked location pointer is passed down to the repository and requires
1346
 
    a repository format with supports_external_lookups = True.
1347
 
 
1348
 
    This format was introduced in bzr 1.6.
1349
 
    """
1350
 
 
1351
 
    def _branch_class(self):
1352
 
        return BzrBranch7
1353
 
 
1354
 
    def get_format_string(self):
1355
 
        """See BranchFormat.get_format_string()."""
1356
 
        return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
1357
 
 
1358
 
    def get_format_description(self):
1359
 
        """See BranchFormat.get_format_description()."""
1360
 
        return "Branch format 7"
1361
 
 
1362
 
    def initialize(self, a_bzrdir):
1363
 
        """Create a branch of this format in a_bzrdir."""
1364
 
        utf8_files = [('last-revision', '0 null:\n'),
1365
 
                      ('branch.conf', ''),
1366
 
                      ('tags', ''),
1367
 
                      ]
1368
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1369
 
 
1370
 
    def __init__(self):
1371
 
        super(BzrBranchFormat7, self).__init__()
1372
 
        self._matchingbzrdir.repository_format = \
1373
 
            RepositoryFormatPackDevelopment1Subtree()
1374
 
 
1375
 
    def supports_stacking(self):
 
1169
    def open(self, a_bzrdir, _found=False):
 
1170
        """Return the branch object for a_bzrdir
 
1171
 
 
1172
        _found is a private parameter, do not use it. It is used to indicate
 
1173
               if format probing has already be done.
 
1174
        """
 
1175
        if not _found:
 
1176
            format = BranchFormat.find_format(a_bzrdir)
 
1177
            assert format.__class__ == self.__class__
 
1178
        transport = a_bzrdir.get_branch_transport(None)
 
1179
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1180
                                                     lockdir.LockDir)
 
1181
        return BzrBranch6(_format=self,
 
1182
                          _control_files=control_files,
 
1183
                          a_bzrdir=a_bzrdir,
 
1184
                          _repository=a_bzrdir.find_repository())
 
1185
 
 
1186
    def supports_tags(self):
1376
1187
        return True
1377
1188
 
1378
1189
 
1443
1254
        """
1444
1255
        if not _found:
1445
1256
            format = BranchFormat.find_format(a_bzrdir)
1446
 
            if format.__class__ != self.__class__:
1447
 
                raise AssertionError("wrong format %r found for %r" %
1448
 
                    (format, self))
 
1257
            assert format.__class__ == self.__class__
1449
1258
        if location is None:
1450
1259
            location = self.get_reference(a_bzrdir)
1451
1260
        real_bzrdir = bzrdir.BzrDir.open(
1467
1276
# and not independently creatable, so are not registered.
1468
1277
__format5 = BzrBranchFormat5()
1469
1278
__format6 = BzrBranchFormat6()
1470
 
__format7 = BzrBranchFormat7()
1471
1279
BranchFormat.register_format(__format5)
1472
1280
BranchFormat.register_format(BranchReferenceFormat())
1473
1281
BranchFormat.register_format(__format6)
1474
 
BranchFormat.register_format(__format7)
1475
1282
BranchFormat.set_default_format(__format6)
1476
1283
_legacy_formats = [BzrBranchFormat4(),
1477
1284
                   ]
1482
1289
    Note that it's "local" in the context of the filesystem; it doesn't
1483
1290
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1484
1291
    it's writable, and can be accessed via the normal filesystem API.
1485
 
 
1486
 
    :ivar _transport: Transport for file operations on this branch's 
1487
 
        control files, typically pointing to the .bzr/branch directory.
1488
 
    :ivar repository: Repository for this branch.
1489
 
    :ivar base: The url of the base directory for this branch; the one 
1490
 
        containing the .bzr directory.
1491
1292
    """
1492
1293
    
1493
1294
    def __init__(self, _format=None,
1494
1295
                 _control_files=None, a_bzrdir=None, _repository=None):
1495
1296
        """Create new branch object at a particular location."""
 
1297
        Branch.__init__(self)
1496
1298
        if a_bzrdir is None:
1497
1299
            raise ValueError('a_bzrdir must be supplied')
1498
1300
        else:
1499
1301
            self.bzrdir = a_bzrdir
 
1302
        # self._transport used to point to the directory containing the
 
1303
        # control directory, but was not used - now it's just the transport
 
1304
        # for the branch control files.  mbp 20070212
1500
1305
        self._base = self.bzrdir.transport.clone('..').base
1501
 
        # XXX: We should be able to just do
1502
 
        #   self.base = self.bzrdir.root_transport.base
1503
 
        # but this does not quite work yet -- mbp 20080522
1504
1306
        self._format = _format
1505
1307
        if _control_files is None:
1506
1308
            raise ValueError('BzrBranch _control_files is None')
1507
1309
        self.control_files = _control_files
1508
1310
        self._transport = _control_files._transport
1509
1311
        self.repository = _repository
1510
 
        Branch.__init__(self)
1511
1312
 
1512
1313
    def __str__(self):
1513
1314
        return '%s(%r)' % (self.__class__.__name__, self.base)
1520
1321
 
1521
1322
    base = property(_get_base, doc="The URL for the root of this branch.")
1522
1323
 
1523
 
    @deprecated_method(deprecated_in((0, 16, 0)))
1524
1324
    def abspath(self, name):
1525
1325
        """See Branch.abspath."""
1526
 
        return self._transport.abspath(name)
 
1326
        return self.control_files._transport.abspath(name)
 
1327
 
 
1328
 
 
1329
    @deprecated_method(zero_sixteen)
 
1330
    @needs_read_lock
 
1331
    def get_root_id(self):
 
1332
        """See Branch.get_root_id."""
 
1333
        tree = self.repository.revision_tree(self.last_revision())
 
1334
        return tree.get_root_id()
1527
1335
 
1528
1336
    def is_locked(self):
1529
1337
        return self.control_files.is_locked()
1574
1382
 
1575
1383
        This performs the actual writing to disk.
1576
1384
        It is intended to be called by BzrBranch5.set_revision_history."""
1577
 
        self._transport.put_bytes(
1578
 
            'revision-history', '\n'.join(history),
1579
 
            mode=self.bzrdir._get_file_mode())
 
1385
        self.control_files.put_bytes(
 
1386
            'revision-history', '\n'.join(history))
1580
1387
 
1581
1388
    @needs_write_lock
1582
1389
    def set_revision_history(self, rev_history):
1583
1390
        """See Branch.set_revision_history."""
1584
1391
        if 'evil' in debug.debug_flags:
1585
1392
            mutter_callsite(3, "set_revision_history scales with history.")
1586
 
        check_not_reserved_id = _mod_revision.check_not_reserved_id
1587
 
        for rev_id in rev_history:
1588
 
            check_not_reserved_id(rev_id)
1589
 
        if Branch.hooks['post_change_branch_tip']:
1590
 
            # Don't calculate the last_revision_info() if there are no hooks
1591
 
            # that will use it.
1592
 
            old_revno, old_revid = self.last_revision_info()
1593
 
        if len(rev_history) == 0:
1594
 
            revid = _mod_revision.NULL_REVISION
1595
 
        else:
1596
 
            revid = rev_history[-1]
1597
 
        self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
 
1393
        self._clear_cached_state()
1598
1394
        self._write_revision_history(rev_history)
1599
 
        self._clear_cached_state()
1600
1395
        self._cache_revision_history(rev_history)
1601
1396
        for hook in Branch.hooks['set_rh']:
1602
1397
            hook(self, rev_history)
1603
 
        if Branch.hooks['post_change_branch_tip']:
1604
 
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1605
1398
 
1606
 
    def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1607
 
        """Run the pre_change_branch_tip hooks."""
1608
 
        hooks = Branch.hooks['pre_change_branch_tip']
1609
 
        if not hooks:
1610
 
            return
1611
 
        old_revno, old_revid = self.last_revision_info()
1612
 
        params = ChangeBranchTipParams(
1613
 
            self, old_revno, new_revno, old_revid, new_revid)
1614
 
        for hook in hooks:
1615
 
            try:
1616
 
                hook(params)
1617
 
            except errors.TipChangeRejected:
1618
 
                raise
1619
 
            except Exception:
1620
 
                exc_info = sys.exc_info()
1621
 
                hook_name = Branch.hooks.get_hook_name(hook)
1622
 
                raise errors.HookFailed(
1623
 
                    'pre_change_branch_tip', hook_name, exc_info)
1624
 
 
1625
 
    def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1626
 
        """Run the post_change_branch_tip hooks."""
1627
 
        hooks = Branch.hooks['post_change_branch_tip']
1628
 
        if not hooks:
1629
 
            return
1630
 
        new_revno, new_revid = self.last_revision_info()
1631
 
        params = ChangeBranchTipParams(
1632
 
            self, old_revno, new_revno, old_revid, new_revid)
1633
 
        for hook in hooks:
1634
 
            hook(params)
1635
 
 
1636
1399
    @needs_write_lock
1637
1400
    def set_last_revision_info(self, revno, revision_id):
1638
1401
        """Set the last revision of this branch.
1645
1408
        configured to check constraints on history, in which case this may not
1646
1409
        be permitted.
1647
1410
        """
1648
 
        revision_id = _mod_revision.ensure_null(revision_id)
1649
 
        # this old format stores the full history, but this api doesn't
1650
 
        # provide it, so we must generate, and might as well check it's
1651
 
        # correct
1652
1411
        history = self._lefthand_history(revision_id)
1653
 
        if len(history) != revno:
1654
 
            raise AssertionError('%d != %d' % (len(history), revno))
 
1412
        assert len(history) == revno, '%d != %d' % (len(history), revno)
1655
1413
        self.set_revision_history(history)
1656
1414
 
1657
1415
    def _gen_revision_history(self):
1658
 
        history = self._transport.get_bytes('revision-history').split('\n')
 
1416
        history = self.control_files.get('revision-history').read().split('\n')
1659
1417
        if history[-1:] == ['']:
1660
1418
            # There shouldn't be a trailing newline, but just in case.
1661
1419
            history.pop()
1666
1424
        if 'evil' in debug.debug_flags:
1667
1425
            mutter_callsite(4, "_lefthand_history scales with history.")
1668
1426
        # stop_revision must be a descendant of last_revision
1669
 
        graph = self.repository.get_graph()
1670
 
        if last_rev is not None:
1671
 
            if not graph.is_ancestor(last_rev, revision_id):
1672
 
                # our previous tip is not merged into stop_revision
1673
 
                raise errors.DivergedBranches(self, other_branch)
 
1427
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1428
        if (last_rev is not None and last_rev != _mod_revision.NULL_REVISION
 
1429
            and last_rev not in stop_graph):
 
1430
            # our previous tip is not merged into stop_revision
 
1431
            raise errors.DivergedBranches(self, other_branch)
1674
1432
        # make a new revision history from the graph
1675
 
        parents_map = graph.get_parent_map([revision_id])
1676
 
        if revision_id not in parents_map:
1677
 
            raise errors.NoSuchRevision(self, revision_id)
1678
1433
        current_rev_id = revision_id
1679
1434
        new_history = []
1680
 
        check_not_reserved_id = _mod_revision.check_not_reserved_id
1681
 
        # Do not include ghosts or graph origin in revision_history
1682
 
        while (current_rev_id in parents_map and
1683
 
               len(parents_map[current_rev_id]) > 0):
1684
 
            check_not_reserved_id(current_rev_id)
 
1435
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1685
1436
            new_history.append(current_rev_id)
1686
 
            current_rev_id = parents_map[current_rev_id][0]
1687
 
            parents_map = graph.get_parent_map([current_rev_id])
 
1437
            current_rev_id_parents = stop_graph[current_rev_id]
 
1438
            try:
 
1439
                current_rev_id = current_rev_id_parents[0]
 
1440
            except IndexError:
 
1441
                current_rev_id = None
1688
1442
        new_history.reverse()
1689
1443
        return new_history
1690
1444
 
1702
1456
        self.set_revision_history(self._lefthand_history(revision_id,
1703
1457
            last_rev, other_branch))
1704
1458
 
 
1459
    @needs_write_lock
 
1460
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1461
        """See Branch.update_revisions."""
 
1462
        other.lock_read()
 
1463
        try:
 
1464
            other_last_revno, other_last_revision = other.last_revision_info()
 
1465
            if stop_revision is None:
 
1466
                stop_revision = other_last_revision
 
1467
                if _mod_revision.is_null(stop_revision):
 
1468
                    # if there are no commits, we're done.
 
1469
                    return
 
1470
            # whats the current last revision, before we fetch [and change it
 
1471
            # possibly]
 
1472
            last_rev = _mod_revision.ensure_null(self.last_revision())
 
1473
            # we fetch here so that we don't process data twice in the common
 
1474
            # case of having something to pull, and so that the check for 
 
1475
            # already merged can operate on the just fetched graph, which will
 
1476
            # be cached in memory.
 
1477
            self.fetch(other, stop_revision)
 
1478
            # Check to see if one is an ancestor of the other
 
1479
            if not overwrite:
 
1480
                heads = self.repository.get_graph().heads([stop_revision,
 
1481
                                                           last_rev])
 
1482
                if heads == set([last_rev]):
 
1483
                    # The current revision is a decendent of the target,
 
1484
                    # nothing to do
 
1485
                    return
 
1486
                elif heads == set([stop_revision, last_rev]):
 
1487
                    # These branches have diverged
 
1488
                    raise errors.DivergedBranches(self, other)
 
1489
                assert heads == set([stop_revision])
 
1490
            if other_last_revision == stop_revision:
 
1491
                self.set_last_revision_info(other_last_revno,
 
1492
                                            other_last_revision)
 
1493
            else:
 
1494
                # TODO: jam 2007-11-29 Is there a way to determine the
 
1495
                #       revno without searching all of history??
 
1496
                if overwrite:
 
1497
                    self.generate_revision_history(stop_revision)
 
1498
                else:
 
1499
                    self.generate_revision_history(stop_revision,
 
1500
                        last_rev=last_rev, other_branch=other)
 
1501
        finally:
 
1502
            other.unlock()
 
1503
 
1705
1504
    def basis_tree(self):
1706
1505
        """See Branch.basis_tree."""
1707
1506
        return self.repository.revision_tree(self.last_revision())
1708
1507
 
1709
1508
    @needs_write_lock
1710
1509
    def pull(self, source, overwrite=False, stop_revision=None,
1711
 
             _hook_master=None, run_hooks=True, possible_transports=None,
1712
 
             _override_hook_target=None):
 
1510
             _hook_master=None, run_hooks=True, possible_transports=None):
1713
1511
        """See Branch.pull.
1714
1512
 
1715
1513
        :param _hook_master: Private parameter - set the branch to 
1716
 
            be supplied as the master to pull hooks.
 
1514
            be supplied as the master to push hooks.
1717
1515
        :param run_hooks: Private parameter - if false, this branch
1718
1516
            is being called because it's the master of the primary branch,
1719
1517
            so it should not run its hooks.
1720
 
        :param _override_hook_target: Private parameter - set the branch to be
1721
 
            supplied as the target_branch to pull hooks.
1722
1518
        """
1723
1519
        result = PullResult()
1724
1520
        result.source_branch = source
1725
 
        if _override_hook_target is None:
1726
 
            result.target_branch = self
1727
 
        else:
1728
 
            result.target_branch = _override_hook_target
 
1521
        result.target_branch = self
1729
1522
        source.lock_read()
1730
1523
        try:
1731
 
            # We assume that during 'pull' the local repository is closer than
1732
 
            # the remote one.
1733
 
            graph = self.repository.get_graph(source.repository)
1734
1524
            result.old_revno, result.old_revid = self.last_revision_info()
1735
 
            self.update_revisions(source, stop_revision, overwrite=overwrite,
1736
 
                                  graph=graph)
 
1525
            self.update_revisions(source, stop_revision, overwrite=overwrite)
1737
1526
            result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1738
1527
            result.new_revno, result.new_revid = self.last_revision_info()
1739
1528
            if _hook_master:
1740
1529
                result.master_branch = _hook_master
1741
 
                result.local_branch = result.target_branch
 
1530
                result.local_branch = self
1742
1531
            else:
1743
 
                result.master_branch = result.target_branch
 
1532
                result.master_branch = self
1744
1533
                result.local_branch = None
1745
1534
            if run_hooks:
1746
1535
                for hook in Branch.hooks['post_pull']:
1753
1542
        _locs = ['parent', 'pull', 'x-pull']
1754
1543
        for l in _locs:
1755
1544
            try:
1756
 
                return self._transport.get_bytes(l).strip('\n')
1757
 
            except errors.NoSuchFile:
 
1545
                return self.control_files.get(l).read().strip('\n')
 
1546
            except NoSuchFile:
1758
1547
                pass
1759
1548
        return None
1760
1549
 
1836
1625
        result.source_branch = self
1837
1626
        result.target_branch = target
1838
1627
        result.old_revno, result.old_revid = target.last_revision_info()
1839
 
 
1840
 
        # We assume that during 'push' this repository is closer than
1841
 
        # the target.
1842
 
        graph = self.repository.get_graph(target.repository)
1843
 
        target.update_revisions(self, stop_revision, overwrite=overwrite,
1844
 
                                graph=graph)
 
1628
        try:
 
1629
            target.update_revisions(self, stop_revision)
 
1630
        except DivergedBranches:
 
1631
            if not overwrite:
 
1632
                raise
 
1633
        if overwrite:
 
1634
            target.set_revision_history(self.revision_history())
1845
1635
        result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1846
1636
        result.new_revno, result.new_revid = target.last_revision_info()
1847
1637
        return result
1848
1638
 
1849
1639
    def get_parent(self):
1850
1640
        """See Branch.get_parent."""
 
1641
 
 
1642
        assert self.base[-1] == '/'
1851
1643
        parent = self._get_parent_location()
1852
1644
        if parent is None:
1853
1645
            return parent
1860
1652
        except errors.InvalidURLJoin, e:
1861
1653
            raise errors.InaccessibleParent(parent, self.base)
1862
1654
 
1863
 
    def get_stacked_on_url(self):
1864
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
1865
 
 
1866
1655
    def set_push_location(self, location):
1867
1656
        """See Branch.set_push_location."""
1868
1657
        self.get_config().set_user_option(
1875
1664
        # TODO: Maybe delete old location files?
1876
1665
        # URLs should never be unicode, even on the local fs,
1877
1666
        # FIXUP this and get_parent in a future branch format bump:
1878
 
        # read and rewrite the file. RBC 20060125
 
1667
        # read and rewrite the file, and have the new format code read
 
1668
        # using .get not .get_utf8. RBC 20060125
1879
1669
        if url is not None:
1880
1670
            if isinstance(url, unicode):
1881
 
                try:
 
1671
                try: 
1882
1672
                    url = url.encode('ascii')
1883
1673
                except UnicodeEncodeError:
1884
1674
                    raise errors.InvalidURL(url,
1889
1679
 
1890
1680
    def _set_parent_location(self, url):
1891
1681
        if url is None:
1892
 
            self._transport.delete('parent')
 
1682
            self.control_files._transport.delete('parent')
1893
1683
        else:
1894
 
            self._transport.put_bytes('parent', url + '\n',
1895
 
                mode=self.bzrdir._get_file_mode())
1896
 
 
1897
 
    def set_stacked_on_url(self, url):
1898
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
1684
            assert isinstance(url, str)
 
1685
            self.control_files.put_bytes('parent', url + '\n')
1899
1686
 
1900
1687
 
1901
1688
class BzrBranch5(BzrBranch):
1904
1691
    It has support for a master_branch which is the data for bound branches.
1905
1692
    """
1906
1693
 
 
1694
    def __init__(self,
 
1695
                 _format,
 
1696
                 _control_files,
 
1697
                 a_bzrdir,
 
1698
                 _repository):
 
1699
        super(BzrBranch5, self).__init__(_format=_format,
 
1700
                                         _control_files=_control_files,
 
1701
                                         a_bzrdir=a_bzrdir,
 
1702
                                         _repository=_repository)
 
1703
        
1907
1704
    @needs_write_lock
1908
1705
    def pull(self, source, overwrite=False, stop_revision=None,
1909
 
             run_hooks=True, possible_transports=None,
1910
 
             _override_hook_target=None):
 
1706
             run_hooks=True, possible_transports=None):
1911
1707
        """Pull from source into self, updating my master if any.
1912
1708
        
1913
1709
        :param run_hooks: Private parameter - if false, this branch
1927
1723
                    run_hooks=False)
1928
1724
            return super(BzrBranch5, self).pull(source, overwrite,
1929
1725
                stop_revision, _hook_master=master_branch,
1930
 
                run_hooks=run_hooks,
1931
 
                _override_hook_target=_override_hook_target)
 
1726
                run_hooks=run_hooks)
1932
1727
        finally:
1933
1728
            if master_branch:
1934
1729
                master_branch.unlock()
1935
1730
 
1936
1731
    def get_bound_location(self):
1937
1732
        try:
1938
 
            return self._transport.get_bytes('bound')[:-1]
 
1733
            return self.control_files.get_utf8('bound').read()[:-1]
1939
1734
        except errors.NoSuchFile:
1940
1735
            return None
1941
1736
 
1967
1762
        :param location: URL to the target branch
1968
1763
        """
1969
1764
        if location:
1970
 
            self._transport.put_bytes('bound', location+'\n',
1971
 
                mode=self.bzrdir._get_file_mode())
 
1765
            self.control_files.put_utf8('bound', location+'\n')
1972
1766
        else:
1973
1767
            try:
1974
 
                self._transport.delete('bound')
1975
 
            except errors.NoSuchFile:
 
1768
                self.control_files._transport.delete('bound')
 
1769
            except NoSuchFile:
1976
1770
                return False
1977
1771
            return True
1978
1772
 
2025
1819
        return None
2026
1820
 
2027
1821
 
2028
 
class BzrBranch7(BzrBranch5):
2029
 
    """A branch with support for a fallback repository."""
2030
 
 
2031
 
    def _get_fallback_repository(self, url):
2032
 
        """Get the repository we fallback to at url."""
2033
 
        url = urlutils.join(self.base, url)
2034
 
        return bzrdir.BzrDir.open(url).open_branch().repository
2035
 
 
2036
 
    def _activate_fallback_location(self, url):
2037
 
        """Activate the branch/repository from url as a fallback repository."""
2038
 
        self.repository.add_fallback_repository(
2039
 
            self._get_fallback_repository(url))
2040
 
 
2041
 
    def _open_hook(self):
 
1822
class BzrBranchExperimental(BzrBranch5):
 
1823
    """Bzr experimental branch format
 
1824
 
 
1825
    This format has:
 
1826
     - a revision-history file.
 
1827
     - a format string
 
1828
     - a lock dir guarding the branch itself
 
1829
     - all of this stored in a branch/ subdirectory
 
1830
     - works with shared repositories.
 
1831
     - a tag dictionary in the branch
 
1832
 
 
1833
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
1834
    only for testing.
 
1835
 
 
1836
    This class acts as it's own BranchFormat.
 
1837
    """
 
1838
 
 
1839
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1840
 
 
1841
    @classmethod
 
1842
    def get_format_string(cls):
 
1843
        """See BranchFormat.get_format_string()."""
 
1844
        return "Bazaar-NG branch format experimental\n"
 
1845
 
 
1846
    @classmethod
 
1847
    def get_format_description(cls):
 
1848
        """See BranchFormat.get_format_description()."""
 
1849
        return "Experimental branch format"
 
1850
 
 
1851
    @classmethod
 
1852
    def get_reference(cls, a_bzrdir):
 
1853
        """Get the target reference of the branch in a_bzrdir.
 
1854
 
 
1855
        format probing must have been completed before calling
 
1856
        this method - it is assumed that the format of the branch
 
1857
        in a_bzrdir is correct.
 
1858
 
 
1859
        :param a_bzrdir: The bzrdir to get the branch data from.
 
1860
        :return: None if the branch is not a reference branch.
 
1861
        """
 
1862
        return None
 
1863
 
 
1864
    @classmethod
 
1865
    def set_reference(self, a_bzrdir, to_branch):
 
1866
        """Set the target reference of the branch in a_bzrdir.
 
1867
 
 
1868
        format probing must have been completed before calling
 
1869
        this method - it is assumed that the format of the branch
 
1870
        in a_bzrdir is correct.
 
1871
 
 
1872
        :param a_bzrdir: The bzrdir to set the branch reference for.
 
1873
        :param to_branch: branch that the checkout is to reference
 
1874
        """
 
1875
        raise NotImplementedError(self.set_reference)
 
1876
 
 
1877
    @classmethod
 
1878
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
 
1879
            lock_class):
 
1880
        branch_transport = a_bzrdir.get_branch_transport(cls)
 
1881
        control_files = lockable_files.LockableFiles(branch_transport,
 
1882
            lock_filename, lock_class)
 
1883
        control_files.create_lock()
 
1884
        control_files.lock_write()
2042
1885
        try:
2043
 
            url = self.get_stacked_on_url()
2044
 
        except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2045
 
            errors.UnstackableBranchFormat):
2046
 
            pass
2047
 
        else:
2048
 
            self._activate_fallback_location(url)
2049
 
 
2050
 
    def _check_stackable_repo(self):
2051
 
        if not self.repository._format.supports_external_lookups:
2052
 
            raise errors.UnstackableRepositoryFormat(self.repository._format,
2053
 
                self.repository.base)
2054
 
 
2055
 
    def __init__(self, *args, **kwargs):
2056
 
        super(BzrBranch7, self).__init__(*args, **kwargs)
2057
 
        self._last_revision_info_cache = None
2058
 
        self._partial_revision_history_cache = []
2059
 
 
2060
 
    def _clear_cached_state(self):
2061
 
        super(BzrBranch7, self)._clear_cached_state()
2062
 
        self._last_revision_info_cache = None
2063
 
        self._partial_revision_history_cache = []
2064
 
 
2065
 
    def _last_revision_info(self):
2066
 
        revision_string = self._transport.get_bytes('last-revision')
 
1886
            for filename, content in utf8_files:
 
1887
                control_files.put_utf8(filename, content)
 
1888
        finally:
 
1889
            control_files.unlock()
 
1890
        
 
1891
    @classmethod
 
1892
    def initialize(cls, a_bzrdir):
 
1893
        """Create a branch of this format in a_bzrdir."""
 
1894
        utf8_files = [('format', cls.get_format_string()),
 
1895
                      ('revision-history', ''),
 
1896
                      ('branch-name', ''),
 
1897
                      ('tags', ''),
 
1898
                      ]
 
1899
        cls._initialize_control_files(a_bzrdir, utf8_files,
 
1900
            'lock', lockdir.LockDir)
 
1901
        return cls.open(a_bzrdir, _found=True)
 
1902
 
 
1903
    @classmethod
 
1904
    def open(cls, a_bzrdir, _found=False):
 
1905
        """Return the branch object for a_bzrdir
 
1906
 
 
1907
        _found is a private parameter, do not use it. It is used to indicate
 
1908
               if format probing has already be done.
 
1909
        """
 
1910
        if not _found:
 
1911
            format = BranchFormat.find_format(a_bzrdir)
 
1912
            assert format.__class__ == cls
 
1913
        transport = a_bzrdir.get_branch_transport(None)
 
1914
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1915
                                                     lockdir.LockDir)
 
1916
        return cls(_format=cls,
 
1917
            _control_files=control_files,
 
1918
            a_bzrdir=a_bzrdir,
 
1919
            _repository=a_bzrdir.find_repository())
 
1920
 
 
1921
    @classmethod
 
1922
    def is_supported(cls):
 
1923
        return True
 
1924
 
 
1925
    def _make_tags(self):
 
1926
        return BasicTags(self)
 
1927
 
 
1928
    @classmethod
 
1929
    def supports_tags(cls):
 
1930
        return True
 
1931
 
 
1932
 
 
1933
BranchFormat.register_format(BzrBranchExperimental)
 
1934
 
 
1935
 
 
1936
class BzrBranch6(BzrBranch5):
 
1937
 
 
1938
    @needs_read_lock
 
1939
    def last_revision_info(self):
 
1940
        revision_string = self.control_files.get('last-revision').read()
2067
1941
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2068
1942
        revision_id = cache_utf8.get_cached_utf8(revision_id)
2069
1943
        revno = int(revno)
2070
1944
        return revno, revision_id
2071
1945
 
 
1946
    def last_revision(self):
 
1947
        """Return last revision id, or None"""
 
1948
        revision_id = self.last_revision_info()[1]
 
1949
        return revision_id
 
1950
 
2072
1951
    def _write_last_revision_info(self, revno, revision_id):
2073
1952
        """Simply write out the revision id, with no checks.
2074
1953
 
2078
1957
        Intended to be called by set_last_revision_info and
2079
1958
        _write_revision_history.
2080
1959
        """
2081
 
        revision_id = _mod_revision.ensure_null(revision_id)
 
1960
        if revision_id is None:
 
1961
            revision_id = 'null:'
2082
1962
        out_string = '%d %s\n' % (revno, revision_id)
2083
 
        self._transport.put_bytes('last-revision', out_string,
2084
 
            mode=self.bzrdir._get_file_mode())
 
1963
        self.control_files.put_bytes('last-revision', out_string)
2085
1964
 
2086
1965
    @needs_write_lock
2087
1966
    def set_last_revision_info(self, revno, revision_id):
2088
 
        revision_id = _mod_revision.ensure_null(revision_id)
2089
 
        old_revno, old_revid = self.last_revision_info()
2090
1967
        if self._get_append_revisions_only():
2091
1968
            self._check_history_violation(revision_id)
2092
 
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
2093
1969
        self._write_last_revision_info(revno, revision_id)
2094
1970
        self._clear_cached_state()
2095
 
        self._last_revision_info_cache = revno, revision_id
2096
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2097
1971
 
2098
1972
    def _check_history_violation(self, revision_id):
2099
1973
        last_revision = _mod_revision.ensure_null(self.last_revision())
2105
1979
    def _gen_revision_history(self):
2106
1980
        """Generate the revision history from last revision
2107
1981
        """
2108
 
        last_revno, last_revision = self.last_revision_info()
2109
 
        self._extend_partial_history(stop_index=last_revno-1)
2110
 
        return list(reversed(self._partial_revision_history_cache))
2111
 
 
2112
 
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
2113
 
        """Extend the partial history to include a given index
2114
 
 
2115
 
        If a stop_index is supplied, stop when that index has been reached.
2116
 
        If a stop_revision is supplied, stop when that revision is
2117
 
        encountered.  Otherwise, stop when the beginning of history is
2118
 
        reached.
2119
 
 
2120
 
        :param stop_index: The index which should be present.  When it is
2121
 
            present, history extension will stop.
2122
 
        :param revision_id: The revision id which should be present.  When
2123
 
            it is encountered, history extension will stop.
2124
 
        """
2125
 
        repo = self.repository
2126
 
        if len(self._partial_revision_history_cache) == 0:
2127
 
            iterator = repo.iter_reverse_revision_history(self.last_revision())
2128
 
        else:
2129
 
            start_revision = self._partial_revision_history_cache[-1]
2130
 
            iterator = repo.iter_reverse_revision_history(start_revision)
2131
 
            #skip the last revision in the list
2132
 
            next_revision = iterator.next()
2133
 
        for revision_id in iterator:
2134
 
            self._partial_revision_history_cache.append(revision_id)
2135
 
            if (stop_index is not None and
2136
 
                len(self._partial_revision_history_cache) > stop_index):
2137
 
                break
2138
 
            if revision_id == stop_revision:
2139
 
                break
 
1982
        history = list(self.repository.iter_reverse_revision_history(
 
1983
            self.last_revision()))
 
1984
        history.reverse()
 
1985
        return history
2140
1986
 
2141
1987
    def _write_revision_history(self, history):
2142
1988
        """Factored out of set_revision_history.
2202
2048
        """See Branch.get_old_bound_location"""
2203
2049
        return self._get_bound_location(False)
2204
2050
 
2205
 
    def get_stacked_on_url(self):
2206
 
        self._check_stackable_repo()
2207
 
        stacked_url = self._get_config_location('stacked_on_location')
2208
 
        if stacked_url is None:
2209
 
            raise errors.NotStacked(self)
2210
 
        return stacked_url
2211
 
 
2212
2051
    def set_append_revisions_only(self, enabled):
2213
2052
        if enabled:
2214
2053
            value = 'True'
2217
2056
        self.get_config().set_user_option('append_revisions_only', value,
2218
2057
            warn_masked=True)
2219
2058
 
2220
 
    def set_stacked_on_url(self, url):
2221
 
        self._check_stackable_repo()
2222
 
        if not url:
2223
 
            try:
2224
 
                old_url = self.get_stacked_on_url()
2225
 
            except (errors.NotStacked, errors.UnstackableBranchFormat,
2226
 
                errors.UnstackableRepositoryFormat):
2227
 
                return
2228
 
            url = ''
2229
 
            # repositories don't offer an interface to remove fallback
2230
 
            # repositories today; take the conceptually simpler option and just
2231
 
            # reopen it.
2232
 
            self.repository = self.bzrdir.find_repository()
2233
 
            # for every revision reference the branch has, ensure it is pulled
2234
 
            # in.
2235
 
            source_repository = self._get_fallback_repository(old_url)
2236
 
            for revision_id in chain([self.last_revision()],
2237
 
                self.tags.get_reverse_tag_dict()):
2238
 
                self.repository.fetch(source_repository, revision_id,
2239
 
                    find_ghosts=True)
2240
 
        else:
2241
 
            self._activate_fallback_location(url)
2242
 
        # write this out after the repository is stacked to avoid setting a
2243
 
        # stacked config that doesn't work.
2244
 
        self._set_config_location('stacked_on_location', url)
2245
 
 
2246
2059
    def _get_append_revisions_only(self):
2247
2060
        value = self.get_config().get_user_option('append_revisions_only')
2248
2061
        return value == 'True'
2279
2092
    def _make_tags(self):
2280
2093
        return BasicTags(self)
2281
2094
 
2282
 
    @needs_write_lock
2283
 
    def generate_revision_history(self, revision_id, last_rev=None,
2284
 
                                  other_branch=None):
2285
 
        """See BzrBranch5.generate_revision_history"""
2286
 
        history = self._lefthand_history(revision_id, last_rev, other_branch)
2287
 
        revno = len(history)
2288
 
        self.set_last_revision_info(revno, revision_id)
2289
 
 
2290
 
    @needs_read_lock
2291
 
    def get_rev_id(self, revno, history=None):
2292
 
        """Find the revision id of the specified revno."""
2293
 
        if revno == 0:
2294
 
            return _mod_revision.NULL_REVISION
2295
 
 
2296
 
        last_revno, last_revision_id = self.last_revision_info()
2297
 
        if revno <= 0 or revno > last_revno:
2298
 
            raise errors.NoSuchRevision(self, revno)
2299
 
 
2300
 
        if history is not None:
2301
 
            return history[revno - 1]
2302
 
 
2303
 
        index = last_revno - revno
2304
 
        if len(self._partial_revision_history_cache) <= index:
2305
 
            self._extend_partial_history(stop_index=index)
2306
 
        if len(self._partial_revision_history_cache) > index:
2307
 
            return self._partial_revision_history_cache[index]
2308
 
        else:
2309
 
            raise errors.NoSuchRevision(self, revno)
2310
 
 
2311
 
    @needs_read_lock
2312
 
    def revision_id_to_revno(self, revision_id):
2313
 
        """Given a revision id, return its revno"""
2314
 
        if _mod_revision.is_null(revision_id):
2315
 
            return 0
2316
 
        try:
2317
 
            index = self._partial_revision_history_cache.index(revision_id)
2318
 
        except ValueError:
2319
 
            self._extend_partial_history(stop_revision=revision_id)
2320
 
            index = len(self._partial_revision_history_cache) - 1
2321
 
            if self._partial_revision_history_cache[index] != revision_id:
2322
 
                raise errors.NoSuchRevision(self, revision_id)
2323
 
        return self.revno() - index
2324
 
 
2325
 
 
2326
 
class BzrBranch6(BzrBranch7):
2327
 
    """See BzrBranchFormat6 for the capabilities of this branch.
2328
 
 
2329
 
    This subclass of BzrBranch7 disables the new features BzrBranch7 added,
2330
 
    i.e. stacking.
2331
 
    """
2332
 
 
2333
 
    def get_stacked_on_url(self):
2334
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
2335
 
 
2336
 
    def set_stacked_on_url(self, url):
2337
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
2338
 
 
2339
2095
 
2340
2096
######################################################################
2341
2097
# results of operations
2359
2115
    :ivar old_revid: Tip revision id before pull.
2360
2116
    :ivar new_revid: Tip revision id after pull.
2361
2117
    :ivar source_branch: Source (local) branch object.
2362
 
    :ivar master_branch: Master branch of the target, or the target if no
2363
 
        Master
2364
 
    :ivar local_branch: target branch if there is a Master, else None
 
2118
    :ivar master_branch: Master branch of the target, or None.
2365
2119
    :ivar target_branch: Target/destination branch object.
2366
 
    :ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2367
2120
    """
2368
2121
 
2369
2122
    def __int__(self):
2371
2124
        return self.new_revno - self.old_revno
2372
2125
 
2373
2126
    def report(self, to_file):
2374
 
        if not is_quiet():
2375
 
            if self.old_revid == self.new_revid:
2376
 
                to_file.write('No revisions to pull.\n')
2377
 
            else:
2378
 
                to_file.write('Now on revision %d.\n' % self.new_revno)
 
2127
        if self.old_revid == self.new_revid:
 
2128
            to_file.write('No revisions to pull.\n')
 
2129
        else:
 
2130
            to_file.write('Now on revision %d.\n' % self.new_revno)
2379
2131
        self._show_tag_conficts(to_file)
2380
2132
 
2381
2133
 
2433
2185
        new_branch = format.open(branch.bzrdir, _found=True)
2434
2186
 
2435
2187
        # Copy source data into target
2436
 
        new_branch._write_last_revision_info(*branch.last_revision_info())
 
2188
        new_branch.set_last_revision_info(*branch.last_revision_info())
2437
2189
        new_branch.set_parent(branch.get_parent())
2438
2190
        new_branch.set_bound_location(branch.get_bound_location())
2439
2191
        new_branch.set_push_location(branch.get_push_location())
2442
2194
        new_branch.tags._set_tag_dict({})
2443
2195
 
2444
2196
        # Copying done; now update target format
2445
 
        new_branch._transport.put_bytes('format',
2446
 
            format.get_format_string(),
2447
 
            mode=new_branch.bzrdir._get_file_mode())
 
2197
        new_branch.control_files.put_utf8('format',
 
2198
            format.get_format_string())
2448
2199
 
2449
2200
        # Clean up old files
2450
 
        new_branch._transport.delete('revision-history')
 
2201
        new_branch.control_files._transport.delete('revision-history')
2451
2202
        try:
2452
2203
            branch.set_parent(None)
2453
 
        except errors.NoSuchFile:
 
2204
        except NoSuchFile:
2454
2205
            pass
2455
2206
        branch.set_bound_location(None)
2456
 
 
2457
 
 
2458
 
class Converter6to7(object):
2459
 
    """Perform an in-place upgrade of format 6 to format 7"""
2460
 
 
2461
 
    def convert(self, branch):
2462
 
        format = BzrBranchFormat7()
2463
 
        branch._set_config_location('stacked_on_location', '')
2464
 
        # update target format
2465
 
        branch._transport.put_bytes('format', format.get_format_string())