~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-20 16:16:34 UTC
  • mfrom: (3123.5.18 hardlinks)
  • Revision ID: pqm@pqm.ubuntu.com-20071220161634-2kcjb650o21ydko4
Accelerate build_tree using similar workingtrees (abentley)

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, accelerator_tree=None):
819
761
        """Create a checkout of a branch.
820
762
        
821
763
        :param to_location: The url to produce the checkout at
826
768
            contents more quickly than the revision tree, i.e. a workingtree.
827
769
            The revision tree will be used for cases where accelerator_tree's
828
770
            content is different.
829
 
        :param hardlink: If true, hard-link files from accelerator_tree,
830
 
            where possible.
831
771
        :return: The tree of the created checkout
832
772
        """
833
773
        t = transport.get_transport(to_location)
848
788
            from_branch=None
849
789
        tree = checkout.create_workingtree(revision_id,
850
790
                                           from_branch=from_branch,
851
 
                                           accelerator_tree=accelerator_tree,
852
 
                                           hardlink=hardlink)
 
791
                                           accelerator_tree=accelerator_tree)
853
792
        basis_tree = tree.basis_tree()
854
793
        basis_tree.lock_read()
855
794
        try:
862
801
            basis_tree.unlock()
863
802
        return tree
864
803
 
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
804
    def reference_parent(self, file_id, path):
874
805
        """Return the parent branch for a tree-reference file_id
875
806
        :param file_id: The file_id of the tree reference
882
813
    def supports_tags(self):
883
814
        return self._format.supports_tags()
884
815
 
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
816
 
921
817
class BranchFormat(object):
922
818
    """An encapsulation of the initialization and open routines for a format.
955
851
            transport = a_bzrdir.get_branch_transport(None)
956
852
            format_string = transport.get("format").read()
957
853
            return klass._formats[format_string]
958
 
        except errors.NoSuchFile:
959
 
            raise errors.NotBranchError(path=transport.base)
 
854
        except NoSuchFile:
 
855
            raise NotBranchError(path=transport.base)
960
856
        except KeyError:
961
 
            raise errors.UnknownFormatError(format=format_string, kind='branch')
 
857
            raise errors.UnknownFormatError(format=format_string)
962
858
 
963
859
    @classmethod
964
860
    def get_default_format(klass):
1022
918
        control_files.create_lock()
1023
919
        control_files.lock_write()
1024
920
        if set_format:
1025
 
            utf8_files += [('format', self.get_format_string())]
 
921
            control_files.put_utf8('format', self.get_format_string())
1026
922
        try:
1027
 
            for (filename, content) in utf8_files:
1028
 
                branch_transport.put_bytes(
1029
 
                    filename, content,
1030
 
                    mode=a_bzrdir._get_file_mode())
 
923
            for file, content in utf8_files:
 
924
                control_files.put_utf8(file, content)
1031
925
        finally:
1032
926
            control_files.unlock()
1033
927
        return self.open(a_bzrdir, _found=True)
1061
955
    def set_default_format(klass, format):
1062
956
        klass._default_format = format
1063
957
 
1064
 
    def supports_stacking(self):
1065
 
        """True if this format records a stacked-on branch."""
1066
 
        return False
1067
 
 
1068
958
    @classmethod
1069
959
    def unregister_format(klass, format):
 
960
        assert klass._formats[format.get_format_string()] is format
1070
961
        del klass._formats[format.get_format_string()]
1071
962
 
1072
963
    def __str__(self):
1076
967
        """True if this format supports tags stored in the branch"""
1077
968
        return False  # by default
1078
969
 
 
970
    # XXX: Probably doesn't really belong here -- mbp 20070212
 
971
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
972
            lock_class):
 
973
        branch_transport = a_bzrdir.get_branch_transport(self)
 
974
        control_files = lockable_files.LockableFiles(branch_transport,
 
975
            lock_filename, lock_class)
 
976
        control_files.create_lock()
 
977
        control_files.lock_write()
 
978
        try:
 
979
            for filename, content in utf8_files:
 
980
                control_files.put_utf8(filename, content)
 
981
        finally:
 
982
            control_files.unlock()
 
983
 
1079
984
 
1080
985
class BranchHooks(Hooks):
1081
986
    """A dictionary mapping hook name to a list of callables for branch hooks.
1138
1043
        # local is the local branch or None, master is the target branch,
1139
1044
        # and an empty branch recieves new_revno of 0, new_revid of None.
1140
1045
        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
1046
 
1154
1047
 
1155
1048
# install the default hooks into the Branch class.
1156
1049
Branch.hooks = BranchHooks()
1157
1050
 
1158
1051
 
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
1052
class BzrBranchFormat4(BranchFormat):
1198
1053
    """Bzr branch format 4.
1199
1054
 
1236
1091
        return "Bazaar-NG branch format 4"
1237
1092
 
1238
1093
 
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)
 
1094
class BzrBranchFormat5(BranchFormat):
 
1095
    """Bzr branch format 5.
 
1096
 
 
1097
    This format has:
 
1098
     - a revision-history file.
 
1099
     - a format string
 
1100
     - a lock dir guarding the branch itself
 
1101
     - all of this stored in a branch/ subdirectory
 
1102
     - works with shared repositories.
 
1103
 
 
1104
    This format is new in bzr 0.8.
 
1105
    """
 
1106
 
 
1107
    def get_format_string(self):
 
1108
        """See BranchFormat.get_format_string()."""
 
1109
        return "Bazaar-NG branch format 5\n"
 
1110
 
 
1111
    def get_format_description(self):
 
1112
        """See BranchFormat.get_format_description()."""
 
1113
        return "Branch format 5"
 
1114
        
 
1115
    def initialize(self, a_bzrdir):
 
1116
        """Create a branch of this format in a_bzrdir."""
 
1117
        utf8_files = [('revision-history', ''),
 
1118
                      ('branch-name', ''),
 
1119
                      ]
 
1120
        return self._initialize_helper(a_bzrdir, utf8_files)
 
1121
 
 
1122
    def __init__(self):
 
1123
        super(BzrBranchFormat5, self).__init__()
 
1124
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1245
1125
 
1246
1126
    def open(self, a_bzrdir, _found=False):
1247
 
        """Return the branch object for a_bzrdir.
 
1127
        """Return the branch object for a_bzrdir
1248
1128
 
1249
1129
        _found is a private parameter, do not use it. It is used to indicate
1250
1130
               if format probing has already be done.
1251
1131
        """
1252
1132
        if not _found:
1253
1133
            format = BranchFormat.find_format(a_bzrdir)
1254
 
            if format.__class__ != self.__class__:
1255
 
                raise AssertionError("wrong format %r found for %r" %
1256
 
                    (format, self))
 
1134
            assert format.__class__ == self.__class__
1257
1135
        try:
1258
1136
            transport = a_bzrdir.get_branch_transport(None)
1259
1137
            control_files = lockable_files.LockableFiles(transport, 'lock',
1260
1138
                                                         lockdir.LockDir)
1261
 
            return self._branch_class()(_format=self,
 
1139
            return BzrBranch5(_format=self,
1262
1140
                              _control_files=control_files,
1263
1141
                              a_bzrdir=a_bzrdir,
1264
1142
                              _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):
 
1143
        except NoSuchFile:
 
1144
            raise NotBranchError(path=transport.base)
 
1145
 
 
1146
 
 
1147
class BzrBranchFormat6(BzrBranchFormat5):
1312
1148
    """Branch format with last-revision and tags.
1313
1149
 
1314
1150
    Unlike previous formats, this has no explicit revision history. Instead,
1319
1155
    and became the default in 0.91.
1320
1156
    """
1321
1157
 
1322
 
    def _branch_class(self):
1323
 
        return BzrBranch6
1324
 
 
1325
1158
    def get_format_string(self):
1326
1159
        """See BranchFormat.get_format_string()."""
1327
1160
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1338
1171
                      ]
1339
1172
        return self._initialize_helper(a_bzrdir, utf8_files)
1340
1173
 
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):
 
1174
    def open(self, a_bzrdir, _found=False):
 
1175
        """Return the branch object for a_bzrdir
 
1176
 
 
1177
        _found is a private parameter, do not use it. It is used to indicate
 
1178
               if format probing has already be done.
 
1179
        """
 
1180
        if not _found:
 
1181
            format = BranchFormat.find_format(a_bzrdir)
 
1182
            assert format.__class__ == self.__class__
 
1183
        transport = a_bzrdir.get_branch_transport(None)
 
1184
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1185
                                                     lockdir.LockDir)
 
1186
        return BzrBranch6(_format=self,
 
1187
                          _control_files=control_files,
 
1188
                          a_bzrdir=a_bzrdir,
 
1189
                          _repository=a_bzrdir.find_repository())
 
1190
 
 
1191
    def supports_tags(self):
1376
1192
        return True
1377
1193
 
1378
1194
 
1443
1259
        """
1444
1260
        if not _found:
1445
1261
            format = BranchFormat.find_format(a_bzrdir)
1446
 
            if format.__class__ != self.__class__:
1447
 
                raise AssertionError("wrong format %r found for %r" %
1448
 
                    (format, self))
 
1262
            assert format.__class__ == self.__class__
1449
1263
        if location is None:
1450
1264
            location = self.get_reference(a_bzrdir)
1451
1265
        real_bzrdir = bzrdir.BzrDir.open(
1467
1281
# and not independently creatable, so are not registered.
1468
1282
__format5 = BzrBranchFormat5()
1469
1283
__format6 = BzrBranchFormat6()
1470
 
__format7 = BzrBranchFormat7()
1471
1284
BranchFormat.register_format(__format5)
1472
1285
BranchFormat.register_format(BranchReferenceFormat())
1473
1286
BranchFormat.register_format(__format6)
1474
 
BranchFormat.register_format(__format7)
1475
1287
BranchFormat.set_default_format(__format6)
1476
1288
_legacy_formats = [BzrBranchFormat4(),
1477
1289
                   ]
1482
1294
    Note that it's "local" in the context of the filesystem; it doesn't
1483
1295
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1484
1296
    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
1297
    """
1492
1298
    
1493
1299
    def __init__(self, _format=None,
1494
1300
                 _control_files=None, a_bzrdir=None, _repository=None):
1495
1301
        """Create new branch object at a particular location."""
 
1302
        Branch.__init__(self)
1496
1303
        if a_bzrdir is None:
1497
1304
            raise ValueError('a_bzrdir must be supplied')
1498
1305
        else:
1499
1306
            self.bzrdir = a_bzrdir
 
1307
        # self._transport used to point to the directory containing the
 
1308
        # control directory, but was not used - now it's just the transport
 
1309
        # for the branch control files.  mbp 20070212
1500
1310
        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
1311
        self._format = _format
1505
1312
        if _control_files is None:
1506
1313
            raise ValueError('BzrBranch _control_files is None')
1507
1314
        self.control_files = _control_files
1508
1315
        self._transport = _control_files._transport
1509
1316
        self.repository = _repository
1510
 
        Branch.__init__(self)
1511
1317
 
1512
1318
    def __str__(self):
1513
1319
        return '%s(%r)' % (self.__class__.__name__, self.base)
1520
1326
 
1521
1327
    base = property(_get_base, doc="The URL for the root of this branch.")
1522
1328
 
1523
 
    @deprecated_method(deprecated_in((0, 16, 0)))
1524
1329
    def abspath(self, name):
1525
1330
        """See Branch.abspath."""
1526
 
        return self._transport.abspath(name)
 
1331
        return self.control_files._transport.abspath(name)
 
1332
 
 
1333
 
 
1334
    @deprecated_method(zero_sixteen)
 
1335
    @needs_read_lock
 
1336
    def get_root_id(self):
 
1337
        """See Branch.get_root_id."""
 
1338
        tree = self.repository.revision_tree(self.last_revision())
 
1339
        return tree.get_root_id()
1527
1340
 
1528
1341
    def is_locked(self):
1529
1342
        return self.control_files.is_locked()
1574
1387
 
1575
1388
        This performs the actual writing to disk.
1576
1389
        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())
 
1390
        self.control_files.put_bytes(
 
1391
            'revision-history', '\n'.join(history))
1580
1392
 
1581
1393
    @needs_write_lock
1582
1394
    def set_revision_history(self, rev_history):
1583
1395
        """See Branch.set_revision_history."""
1584
1396
        if 'evil' in debug.debug_flags:
1585
1397
            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)
 
1398
        self._clear_cached_state()
1598
1399
        self._write_revision_history(rev_history)
1599
 
        self._clear_cached_state()
1600
1400
        self._cache_revision_history(rev_history)
1601
1401
        for hook in Branch.hooks['set_rh']:
1602
1402
            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
1403
 
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
1404
    @needs_write_lock
1637
1405
    def set_last_revision_info(self, revno, revision_id):
1638
1406
        """Set the last revision of this branch.
1645
1413
        configured to check constraints on history, in which case this may not
1646
1414
        be permitted.
1647
1415
        """
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
1416
        history = self._lefthand_history(revision_id)
1653
 
        if len(history) != revno:
1654
 
            raise AssertionError('%d != %d' % (len(history), revno))
 
1417
        assert len(history) == revno, '%d != %d' % (len(history), revno)
1655
1418
        self.set_revision_history(history)
1656
1419
 
1657
1420
    def _gen_revision_history(self):
1658
 
        history = self._transport.get_bytes('revision-history').split('\n')
 
1421
        history = self.control_files.get('revision-history').read().split('\n')
1659
1422
        if history[-1:] == ['']:
1660
1423
            # There shouldn't be a trailing newline, but just in case.
1661
1424
            history.pop()
1666
1429
        if 'evil' in debug.debug_flags:
1667
1430
            mutter_callsite(4, "_lefthand_history scales with history.")
1668
1431
        # 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)
 
1432
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1433
        if (last_rev is not None and last_rev != _mod_revision.NULL_REVISION
 
1434
            and last_rev not in stop_graph):
 
1435
            # our previous tip is not merged into stop_revision
 
1436
            raise errors.DivergedBranches(self, other_branch)
1674
1437
        # 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
1438
        current_rev_id = revision_id
1679
1439
        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)
 
1440
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1685
1441
            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])
 
1442
            current_rev_id_parents = stop_graph[current_rev_id]
 
1443
            try:
 
1444
                current_rev_id = current_rev_id_parents[0]
 
1445
            except IndexError:
 
1446
                current_rev_id = None
1688
1447
        new_history.reverse()
1689
1448
        return new_history
1690
1449
 
1702
1461
        self.set_revision_history(self._lefthand_history(revision_id,
1703
1462
            last_rev, other_branch))
1704
1463
 
 
1464
    @needs_write_lock
 
1465
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1466
        """See Branch.update_revisions."""
 
1467
        other.lock_read()
 
1468
        try:
 
1469
            other_last_revno, other_last_revision = other.last_revision_info()
 
1470
            if stop_revision is None:
 
1471
                stop_revision = other_last_revision
 
1472
                if _mod_revision.is_null(stop_revision):
 
1473
                    # if there are no commits, we're done.
 
1474
                    return
 
1475
            # whats the current last revision, before we fetch [and change it
 
1476
            # possibly]
 
1477
            last_rev = _mod_revision.ensure_null(self.last_revision())
 
1478
            # we fetch here so that we don't process data twice in the common
 
1479
            # case of having something to pull, and so that the check for 
 
1480
            # already merged can operate on the just fetched graph, which will
 
1481
            # be cached in memory.
 
1482
            self.fetch(other, stop_revision)
 
1483
            # Check to see if one is an ancestor of the other
 
1484
            if not overwrite:
 
1485
                heads = self.repository.get_graph().heads([stop_revision,
 
1486
                                                           last_rev])
 
1487
                if heads == set([last_rev]):
 
1488
                    # The current revision is a decendent of the target,
 
1489
                    # nothing to do
 
1490
                    return
 
1491
                elif heads == set([stop_revision, last_rev]):
 
1492
                    # These branches have diverged
 
1493
                    raise errors.DivergedBranches(self, other)
 
1494
                assert heads == set([stop_revision])
 
1495
            if other_last_revision == stop_revision:
 
1496
                self.set_last_revision_info(other_last_revno,
 
1497
                                            other_last_revision)
 
1498
            else:
 
1499
                # TODO: jam 2007-11-29 Is there a way to determine the
 
1500
                #       revno without searching all of history??
 
1501
                if overwrite:
 
1502
                    self.generate_revision_history(stop_revision)
 
1503
                else:
 
1504
                    self.generate_revision_history(stop_revision,
 
1505
                        last_rev=last_rev, other_branch=other)
 
1506
        finally:
 
1507
            other.unlock()
 
1508
 
1705
1509
    def basis_tree(self):
1706
1510
        """See Branch.basis_tree."""
1707
1511
        return self.repository.revision_tree(self.last_revision())
1708
1512
 
1709
1513
    @needs_write_lock
1710
1514
    def pull(self, source, overwrite=False, stop_revision=None,
1711
 
             _hook_master=None, run_hooks=True, possible_transports=None,
1712
 
             _override_hook_target=None):
 
1515
             _hook_master=None, run_hooks=True, possible_transports=None):
1713
1516
        """See Branch.pull.
1714
1517
 
1715
1518
        :param _hook_master: Private parameter - set the branch to 
1716
 
            be supplied as the master to pull hooks.
 
1519
            be supplied as the master to push hooks.
1717
1520
        :param run_hooks: Private parameter - if false, this branch
1718
1521
            is being called because it's the master of the primary branch,
1719
1522
            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
1523
        """
1723
1524
        result = PullResult()
1724
1525
        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
 
1526
        result.target_branch = self
1729
1527
        source.lock_read()
1730
1528
        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
1529
            result.old_revno, result.old_revid = self.last_revision_info()
1735
 
            self.update_revisions(source, stop_revision, overwrite=overwrite,
1736
 
                                  graph=graph)
 
1530
            self.update_revisions(source, stop_revision, overwrite=overwrite)
1737
1531
            result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1738
1532
            result.new_revno, result.new_revid = self.last_revision_info()
1739
1533
            if _hook_master:
1740
1534
                result.master_branch = _hook_master
1741
 
                result.local_branch = result.target_branch
 
1535
                result.local_branch = self
1742
1536
            else:
1743
 
                result.master_branch = result.target_branch
 
1537
                result.master_branch = self
1744
1538
                result.local_branch = None
1745
1539
            if run_hooks:
1746
1540
                for hook in Branch.hooks['post_pull']:
1753
1547
        _locs = ['parent', 'pull', 'x-pull']
1754
1548
        for l in _locs:
1755
1549
            try:
1756
 
                return self._transport.get_bytes(l).strip('\n')
1757
 
            except errors.NoSuchFile:
 
1550
                return self.control_files.get(l).read().strip('\n')
 
1551
            except NoSuchFile:
1758
1552
                pass
1759
1553
        return None
1760
1554
 
1836
1630
        result.source_branch = self
1837
1631
        result.target_branch = target
1838
1632
        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)
 
1633
        try:
 
1634
            target.update_revisions(self, stop_revision)
 
1635
        except DivergedBranches:
 
1636
            if not overwrite:
 
1637
                raise
 
1638
        if overwrite:
 
1639
            target.set_revision_history(self.revision_history())
1845
1640
        result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1846
1641
        result.new_revno, result.new_revid = target.last_revision_info()
1847
1642
        return result
1848
1643
 
1849
1644
    def get_parent(self):
1850
1645
        """See Branch.get_parent."""
 
1646
 
 
1647
        assert self.base[-1] == '/'
1851
1648
        parent = self._get_parent_location()
1852
1649
        if parent is None:
1853
1650
            return parent
1860
1657
        except errors.InvalidURLJoin, e:
1861
1658
            raise errors.InaccessibleParent(parent, self.base)
1862
1659
 
1863
 
    def get_stacked_on_url(self):
1864
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
1865
 
 
1866
1660
    def set_push_location(self, location):
1867
1661
        """See Branch.set_push_location."""
1868
1662
        self.get_config().set_user_option(
1875
1669
        # TODO: Maybe delete old location files?
1876
1670
        # URLs should never be unicode, even on the local fs,
1877
1671
        # FIXUP this and get_parent in a future branch format bump:
1878
 
        # read and rewrite the file. RBC 20060125
 
1672
        # read and rewrite the file, and have the new format code read
 
1673
        # using .get not .get_utf8. RBC 20060125
1879
1674
        if url is not None:
1880
1675
            if isinstance(url, unicode):
1881
 
                try:
 
1676
                try: 
1882
1677
                    url = url.encode('ascii')
1883
1678
                except UnicodeEncodeError:
1884
1679
                    raise errors.InvalidURL(url,
1889
1684
 
1890
1685
    def _set_parent_location(self, url):
1891
1686
        if url is None:
1892
 
            self._transport.delete('parent')
 
1687
            self.control_files._transport.delete('parent')
1893
1688
        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)
 
1689
            assert isinstance(url, str)
 
1690
            self.control_files.put_bytes('parent', url + '\n')
1899
1691
 
1900
1692
 
1901
1693
class BzrBranch5(BzrBranch):
1904
1696
    It has support for a master_branch which is the data for bound branches.
1905
1697
    """
1906
1698
 
 
1699
    def __init__(self,
 
1700
                 _format,
 
1701
                 _control_files,
 
1702
                 a_bzrdir,
 
1703
                 _repository):
 
1704
        super(BzrBranch5, self).__init__(_format=_format,
 
1705
                                         _control_files=_control_files,
 
1706
                                         a_bzrdir=a_bzrdir,
 
1707
                                         _repository=_repository)
 
1708
        
1907
1709
    @needs_write_lock
1908
1710
    def pull(self, source, overwrite=False, stop_revision=None,
1909
 
             run_hooks=True, possible_transports=None,
1910
 
             _override_hook_target=None):
 
1711
             run_hooks=True, possible_transports=None):
1911
1712
        """Pull from source into self, updating my master if any.
1912
1713
        
1913
1714
        :param run_hooks: Private parameter - if false, this branch
1927
1728
                    run_hooks=False)
1928
1729
            return super(BzrBranch5, self).pull(source, overwrite,
1929
1730
                stop_revision, _hook_master=master_branch,
1930
 
                run_hooks=run_hooks,
1931
 
                _override_hook_target=_override_hook_target)
 
1731
                run_hooks=run_hooks)
1932
1732
        finally:
1933
1733
            if master_branch:
1934
1734
                master_branch.unlock()
1935
1735
 
1936
1736
    def get_bound_location(self):
1937
1737
        try:
1938
 
            return self._transport.get_bytes('bound')[:-1]
 
1738
            return self.control_files.get_utf8('bound').read()[:-1]
1939
1739
        except errors.NoSuchFile:
1940
1740
            return None
1941
1741
 
1967
1767
        :param location: URL to the target branch
1968
1768
        """
1969
1769
        if location:
1970
 
            self._transport.put_bytes('bound', location+'\n',
1971
 
                mode=self.bzrdir._get_file_mode())
 
1770
            self.control_files.put_utf8('bound', location+'\n')
1972
1771
        else:
1973
1772
            try:
1974
 
                self._transport.delete('bound')
1975
 
            except errors.NoSuchFile:
 
1773
                self.control_files._transport.delete('bound')
 
1774
            except NoSuchFile:
1976
1775
                return False
1977
1776
            return True
1978
1777
 
2025
1824
        return None
2026
1825
 
2027
1826
 
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):
 
1827
class BzrBranchExperimental(BzrBranch5):
 
1828
    """Bzr experimental branch format
 
1829
 
 
1830
    This format has:
 
1831
     - a revision-history file.
 
1832
     - a format string
 
1833
     - a lock dir guarding the branch itself
 
1834
     - all of this stored in a branch/ subdirectory
 
1835
     - works with shared repositories.
 
1836
     - a tag dictionary in the branch
 
1837
 
 
1838
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
1839
    only for testing.
 
1840
 
 
1841
    This class acts as it's own BranchFormat.
 
1842
    """
 
1843
 
 
1844
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1845
 
 
1846
    @classmethod
 
1847
    def get_format_string(cls):
 
1848
        """See BranchFormat.get_format_string()."""
 
1849
        return "Bazaar-NG branch format experimental\n"
 
1850
 
 
1851
    @classmethod
 
1852
    def get_format_description(cls):
 
1853
        """See BranchFormat.get_format_description()."""
 
1854
        return "Experimental branch format"
 
1855
 
 
1856
    @classmethod
 
1857
    def get_reference(cls, a_bzrdir):
 
1858
        """Get the target reference of the branch in a_bzrdir.
 
1859
 
 
1860
        format probing must have been completed before calling
 
1861
        this method - it is assumed that the format of the branch
 
1862
        in a_bzrdir is correct.
 
1863
 
 
1864
        :param a_bzrdir: The bzrdir to get the branch data from.
 
1865
        :return: None if the branch is not a reference branch.
 
1866
        """
 
1867
        return None
 
1868
 
 
1869
    @classmethod
 
1870
    def set_reference(self, a_bzrdir, to_branch):
 
1871
        """Set the target reference of the branch in a_bzrdir.
 
1872
 
 
1873
        format probing must have been completed before calling
 
1874
        this method - it is assumed that the format of the branch
 
1875
        in a_bzrdir is correct.
 
1876
 
 
1877
        :param a_bzrdir: The bzrdir to set the branch reference for.
 
1878
        :param to_branch: branch that the checkout is to reference
 
1879
        """
 
1880
        raise NotImplementedError(self.set_reference)
 
1881
 
 
1882
    @classmethod
 
1883
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
 
1884
            lock_class):
 
1885
        branch_transport = a_bzrdir.get_branch_transport(cls)
 
1886
        control_files = lockable_files.LockableFiles(branch_transport,
 
1887
            lock_filename, lock_class)
 
1888
        control_files.create_lock()
 
1889
        control_files.lock_write()
2042
1890
        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')
 
1891
            for filename, content in utf8_files:
 
1892
                control_files.put_utf8(filename, content)
 
1893
        finally:
 
1894
            control_files.unlock()
 
1895
        
 
1896
    @classmethod
 
1897
    def initialize(cls, a_bzrdir):
 
1898
        """Create a branch of this format in a_bzrdir."""
 
1899
        utf8_files = [('format', cls.get_format_string()),
 
1900
                      ('revision-history', ''),
 
1901
                      ('branch-name', ''),
 
1902
                      ('tags', ''),
 
1903
                      ]
 
1904
        cls._initialize_control_files(a_bzrdir, utf8_files,
 
1905
            'lock', lockdir.LockDir)
 
1906
        return cls.open(a_bzrdir, _found=True)
 
1907
 
 
1908
    @classmethod
 
1909
    def open(cls, a_bzrdir, _found=False):
 
1910
        """Return the branch object for a_bzrdir
 
1911
 
 
1912
        _found is a private parameter, do not use it. It is used to indicate
 
1913
               if format probing has already be done.
 
1914
        """
 
1915
        if not _found:
 
1916
            format = BranchFormat.find_format(a_bzrdir)
 
1917
            assert format.__class__ == cls
 
1918
        transport = a_bzrdir.get_branch_transport(None)
 
1919
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1920
                                                     lockdir.LockDir)
 
1921
        return cls(_format=cls,
 
1922
            _control_files=control_files,
 
1923
            a_bzrdir=a_bzrdir,
 
1924
            _repository=a_bzrdir.find_repository())
 
1925
 
 
1926
    @classmethod
 
1927
    def is_supported(cls):
 
1928
        return True
 
1929
 
 
1930
    def _make_tags(self):
 
1931
        return BasicTags(self)
 
1932
 
 
1933
    @classmethod
 
1934
    def supports_tags(cls):
 
1935
        return True
 
1936
 
 
1937
 
 
1938
BranchFormat.register_format(BzrBranchExperimental)
 
1939
 
 
1940
 
 
1941
class BzrBranch6(BzrBranch5):
 
1942
 
 
1943
    @needs_read_lock
 
1944
    def last_revision_info(self):
 
1945
        revision_string = self.control_files.get('last-revision').read()
2067
1946
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2068
1947
        revision_id = cache_utf8.get_cached_utf8(revision_id)
2069
1948
        revno = int(revno)
2070
1949
        return revno, revision_id
2071
1950
 
 
1951
    def last_revision(self):
 
1952
        """Return last revision id, or None"""
 
1953
        revision_id = self.last_revision_info()[1]
 
1954
        return revision_id
 
1955
 
2072
1956
    def _write_last_revision_info(self, revno, revision_id):
2073
1957
        """Simply write out the revision id, with no checks.
2074
1958
 
2078
1962
        Intended to be called by set_last_revision_info and
2079
1963
        _write_revision_history.
2080
1964
        """
2081
 
        revision_id = _mod_revision.ensure_null(revision_id)
 
1965
        if revision_id is None:
 
1966
            revision_id = 'null:'
2082
1967
        out_string = '%d %s\n' % (revno, revision_id)
2083
 
        self._transport.put_bytes('last-revision', out_string,
2084
 
            mode=self.bzrdir._get_file_mode())
 
1968
        self.control_files.put_bytes('last-revision', out_string)
2085
1969
 
2086
1970
    @needs_write_lock
2087
1971
    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
1972
        if self._get_append_revisions_only():
2091
1973
            self._check_history_violation(revision_id)
2092
 
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
2093
1974
        self._write_last_revision_info(revno, revision_id)
2094
1975
        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
1976
 
2098
1977
    def _check_history_violation(self, revision_id):
2099
1978
        last_revision = _mod_revision.ensure_null(self.last_revision())
2105
1984
    def _gen_revision_history(self):
2106
1985
        """Generate the revision history from last revision
2107
1986
        """
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
 
1987
        history = list(self.repository.iter_reverse_revision_history(
 
1988
            self.last_revision()))
 
1989
        history.reverse()
 
1990
        return history
2140
1991
 
2141
1992
    def _write_revision_history(self, history):
2142
1993
        """Factored out of set_revision_history.
2202
2053
        """See Branch.get_old_bound_location"""
2203
2054
        return self._get_bound_location(False)
2204
2055
 
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
2056
    def set_append_revisions_only(self, enabled):
2213
2057
        if enabled:
2214
2058
            value = 'True'
2217
2061
        self.get_config().set_user_option('append_revisions_only', value,
2218
2062
            warn_masked=True)
2219
2063
 
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
2064
    def _get_append_revisions_only(self):
2247
2065
        value = self.get_config().get_user_option('append_revisions_only')
2248
2066
        return value == 'True'
2279
2097
    def _make_tags(self):
2280
2098
        return BasicTags(self)
2281
2099
 
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
2100
 
2340
2101
######################################################################
2341
2102
# results of operations
2359
2120
    :ivar old_revid: Tip revision id before pull.
2360
2121
    :ivar new_revid: Tip revision id after pull.
2361
2122
    :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
 
2123
    :ivar master_branch: Master branch of the target, or None.
2365
2124
    :ivar target_branch: Target/destination branch object.
2366
 
    :ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2367
2125
    """
2368
2126
 
2369
2127
    def __int__(self):
2371
2129
        return self.new_revno - self.old_revno
2372
2130
 
2373
2131
    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)
 
2132
        if self.old_revid == self.new_revid:
 
2133
            to_file.write('No revisions to pull.\n')
 
2134
        else:
 
2135
            to_file.write('Now on revision %d.\n' % self.new_revno)
2379
2136
        self._show_tag_conficts(to_file)
2380
2137
 
2381
2138
 
2433
2190
        new_branch = format.open(branch.bzrdir, _found=True)
2434
2191
 
2435
2192
        # Copy source data into target
2436
 
        new_branch._write_last_revision_info(*branch.last_revision_info())
 
2193
        new_branch.set_last_revision_info(*branch.last_revision_info())
2437
2194
        new_branch.set_parent(branch.get_parent())
2438
2195
        new_branch.set_bound_location(branch.get_bound_location())
2439
2196
        new_branch.set_push_location(branch.get_push_location())
2442
2199
        new_branch.tags._set_tag_dict({})
2443
2200
 
2444
2201
        # 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())
 
2202
        new_branch.control_files.put_utf8('format',
 
2203
            format.get_format_string())
2448
2204
 
2449
2205
        # Clean up old files
2450
 
        new_branch._transport.delete('revision-history')
 
2206
        new_branch.control_files._transport.delete('revision-history')
2451
2207
        try:
2452
2208
            branch.set_parent(None)
2453
 
        except errors.NoSuchFile:
 
2209
        except NoSuchFile:
2454
2210
            pass
2455
2211
        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())