~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2007-06-28 02:43:50 UTC
  • mfrom: (2553 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2558.
  • Revision ID: robertc@robertcollins.net-20070628024350-z8bdm0y6yz2uyf4o
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
        revision as _mod_revision,
36
36
        transport,
37
37
        tree,
 
38
        tsort,
38
39
        ui,
39
40
        urlutils,
40
41
        )
41
42
from bzrlib.config import BranchConfig, TreeConfig
42
43
from bzrlib.lockable_files import LockableFiles, TransportLock
 
44
from bzrlib.tag import (
 
45
    BasicTags,
 
46
    DisabledTags,
 
47
    )
43
48
""")
44
49
 
45
50
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
55
                           NotBranchError, UninitializableFormat,
51
56
                           UnlistableStore, UnlistableBranch,
52
57
                           )
 
58
from bzrlib.hooks import Hooks
53
59
from bzrlib.symbol_versioning import (deprecated_function,
54
60
                                      deprecated_method,
55
61
                                      DEPRECATED_PARAMETER,
56
62
                                      deprecated_passed,
57
 
                                      zero_eight, zero_nine,
 
63
                                      zero_eight, zero_nine, zero_sixteen,
58
64
                                      )
59
65
from bzrlib.trace import mutter, note
60
66
 
61
67
 
62
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
63
69
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
64
 
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
70
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
65
71
 
66
72
 
67
73
# TODO: Maybe include checks for common corruption of newlines, etc?
88
94
    # - RBC 20060112
89
95
    base = None
90
96
 
 
97
    # override this to set the strategy for storing tags
 
98
    def _make_tags(self):
 
99
        return DisabledTags(self)
 
100
 
91
101
    def __init__(self, *ignored, **ignored_too):
92
 
        raise NotImplementedError('The Branch class is abstract')
 
102
        self.tags = self._make_tags()
 
103
        self._revision_history_cache = None
 
104
        self._revision_id_to_revno_cache = None
93
105
 
94
106
    def break_lock(self):
95
107
        """Break a lock if one is present from another instance.
185
197
    def get_physical_lock_status(self):
186
198
        raise NotImplementedError(self.get_physical_lock_status)
187
199
 
 
200
    @needs_read_lock
 
201
    def get_revision_id_to_revno_map(self):
 
202
        """Return the revision_id => dotted revno map.
 
203
 
 
204
        This will be regenerated on demand, but will be cached.
 
205
 
 
206
        :return: A dictionary mapping revision_id => dotted revno.
 
207
            This dictionary should not be modified by the caller.
 
208
        """
 
209
        if self._revision_id_to_revno_cache is not None:
 
210
            mapping = self._revision_id_to_revno_cache
 
211
        else:
 
212
            mapping = self._gen_revno_map()
 
213
            self._cache_revision_id_to_revno(mapping)
 
214
        # TODO: jam 20070417 Since this is being cached, should we be returning
 
215
        #       a copy?
 
216
        # I would rather not, and instead just declare that users should not
 
217
        # modify the return value.
 
218
        return mapping
 
219
 
 
220
    def _gen_revno_map(self):
 
221
        """Create a new mapping from revision ids to dotted revnos.
 
222
 
 
223
        Dotted revnos are generated based on the current tip in the revision
 
224
        history.
 
225
        This is the worker function for get_revision_id_to_revno_map, which
 
226
        just caches the return value.
 
227
 
 
228
        :return: A dictionary mapping revision_id => dotted revno.
 
229
        """
 
230
        last_revision = self.last_revision()
 
231
        revision_graph = self.repository.get_revision_graph(last_revision)
 
232
        merge_sorted_revisions = tsort.merge_sort(
 
233
            revision_graph,
 
234
            last_revision,
 
235
            None,
 
236
            generate_revno=True)
 
237
        revision_id_to_revno = dict((rev_id, revno)
 
238
                                    for seq_num, rev_id, depth, revno, end_of_merge
 
239
                                     in merge_sorted_revisions)
 
240
        return revision_id_to_revno
 
241
 
 
242
    def leave_lock_in_place(self):
 
243
        """Tell this branch object not to release the physical lock when this
 
244
        object is unlocked.
 
245
        
 
246
        If lock_write doesn't return a token, then this method is not supported.
 
247
        """
 
248
        self.control_files.leave_in_place()
 
249
 
 
250
    def dont_leave_lock_in_place(self):
 
251
        """Tell this branch object to release the physical lock when this
 
252
        object is unlocked, even if it didn't originally acquire it.
 
253
 
 
254
        If lock_write doesn't return a token, then this method is not supported.
 
255
        """
 
256
        self.control_files.dont_leave_in_place()
 
257
 
188
258
    def abspath(self, name):
189
259
        """Return absolute filename for something in the branch
190
260
        
288
358
            raise InvalidRevisionNumber(revno)
289
359
        return self.repository.get_revision_delta(rh[revno-1])
290
360
 
 
361
    @deprecated_method(zero_sixteen)
291
362
    def get_root_id(self):
292
 
        """Return the id of this branches root"""
 
363
        """Return the id of this branches root
 
364
 
 
365
        Deprecated: branches don't have root ids-- trees do.
 
366
        Use basis_tree().get_root_id() instead.
 
367
        """
293
368
        raise NotImplementedError(self.get_root_id)
294
369
 
295
370
    def print_file(self, file, revision_id):
302
377
    def set_revision_history(self, rev_history):
303
378
        raise NotImplementedError(self.set_revision_history)
304
379
 
 
380
    def _cache_revision_history(self, rev_history):
 
381
        """Set the cached revision history to rev_history.
 
382
 
 
383
        The revision_history method will use this cache to avoid regenerating
 
384
        the revision history.
 
385
 
 
386
        This API is semi-public; it only for use by subclasses, all other code
 
387
        should consider it to be private.
 
388
        """
 
389
        self._revision_history_cache = rev_history
 
390
 
 
391
    def _cache_revision_id_to_revno(self, revision_id_to_revno):
 
392
        """Set the cached revision_id => revno map to revision_id_to_revno.
 
393
 
 
394
        This API is semi-public; it only for use by subclasses, all other code
 
395
        should consider it to be private.
 
396
        """
 
397
        self._revision_id_to_revno_cache = revision_id_to_revno
 
398
 
 
399
    def _clear_cached_state(self):
 
400
        """Clear any cached data on this branch, e.g. cached revision history.
 
401
 
 
402
        This means the next call to revision_history will need to call
 
403
        _gen_revision_history.
 
404
 
 
405
        This API is semi-public; it only for use by subclasses, all other code
 
406
        should consider it to be private.
 
407
        """
 
408
        self._revision_history_cache = None
 
409
        self._revision_id_to_revno_cache = None
 
410
 
 
411
    def _gen_revision_history(self):
 
412
        """Return sequence of revision hashes on to this branch.
 
413
        
 
414
        Unlike revision_history, this method always regenerates or rereads the
 
415
        revision history, i.e. it does not cache the result, so repeated calls
 
416
        may be expensive.
 
417
 
 
418
        Concrete subclasses should override this instead of revision_history so
 
419
        that subclasses do not need to deal with caching logic.
 
420
        
 
421
        This API is semi-public; it only for use by subclasses, all other code
 
422
        should consider it to be private.
 
423
        """
 
424
        raise NotImplementedError(self._gen_revision_history)
 
425
 
 
426
    @needs_read_lock
305
427
    def revision_history(self):
306
 
        """Return sequence of revision hashes on to this branch."""
307
 
        raise NotImplementedError(self.revision_history)
 
428
        """Return sequence of revision hashes on to this branch.
 
429
        
 
430
        This method will cache the revision history for as long as it is safe to
 
431
        do so.
 
432
        """
 
433
        if self._revision_history_cache is not None:
 
434
            history = self._revision_history_cache
 
435
        else:
 
436
            history = self._gen_revision_history()
 
437
            self._cache_revision_history(history)
 
438
        return list(history)
308
439
 
309
440
    def revno(self):
310
441
        """Return current revision number for this branch.
378
509
        """Given a revision id, return its revno"""
379
510
        if revision_id is None:
380
511
            return 0
 
512
        revision_id = osutils.safe_revision_id(revision_id)
381
513
        history = self.revision_history()
382
514
        try:
383
515
            return history.index(revision_id) + 1
384
516
        except ValueError:
385
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
517
            raise errors.NoSuchRevision(self, revision_id)
386
518
 
387
519
    def get_rev_id(self, revno, history=None):
388
520
        """Find the revision id of the specified revno."""
391
523
        if history is None:
392
524
            history = self.revision_history()
393
525
        if revno <= 0 or revno > len(history):
394
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
526
            raise errors.NoSuchRevision(self, revno)
395
527
        return history[revno - 1]
396
528
 
397
529
    def pull(self, source, overwrite=False, stop_revision=None):
398
530
        """Mirror source into this branch.
399
531
 
400
532
        This branch is considered to be 'local', having low latency.
 
533
 
 
534
        :returns: PullResult instance
401
535
        """
402
536
        raise NotImplementedError(self.pull)
403
537
 
444
578
        """
445
579
        raise NotImplementedError(self.get_parent)
446
580
 
 
581
    def _set_config_location(self, name, url, config=None,
 
582
                             make_relative=False):
 
583
        if config is None:
 
584
            config = self.get_config()
 
585
        if url is None:
 
586
            url = ''
 
587
        elif make_relative:
 
588
            url = urlutils.relative_url(self.base, url)
 
589
        config.set_user_option(name, url)
 
590
 
 
591
    def _get_config_location(self, name, config=None):
 
592
        if config is None:
 
593
            config = self.get_config()
 
594
        location = config.get_user_option(name)
 
595
        if location == '':
 
596
            location = None
 
597
        return location
 
598
 
447
599
    def get_submit_branch(self):
448
600
        """Return the submit location of the branch.
449
601
 
462
614
        """
463
615
        self.get_config().set_user_option('submit_branch', location)
464
616
 
 
617
    def get_public_branch(self):
 
618
        """Return the public location of the branch.
 
619
 
 
620
        This is is used by merge directives.
 
621
        """
 
622
        return self._get_config_location('public_branch')
 
623
 
 
624
    def set_public_branch(self, location):
 
625
        """Return the submit location of the branch.
 
626
 
 
627
        This is the default location for bundle.  The usual
 
628
        pattern is that the user can override it by specifying a
 
629
        location.
 
630
        """
 
631
        self._set_config_location('public_branch', location)
 
632
 
465
633
    def get_push_location(self):
466
634
        """Return the None or the location to push this branch to."""
467
 
        raise NotImplementedError(self.get_push_location)
 
635
        push_loc = self.get_config().get_user_option('push_location')
 
636
        return push_loc
468
637
 
469
638
    def set_push_location(self, location):
470
639
        """Set a new push location for this branch."""
498
667
            raise InvalidRevisionNumber(revno)
499
668
 
500
669
    @needs_read_lock
501
 
    def clone(self, *args, **kwargs):
 
670
    def clone(self, to_bzrdir, revision_id=None):
502
671
        """Clone this branch into to_bzrdir preserving all semantic values.
503
672
        
504
673
        revision_id: if not None, the revision history in the new branch will
505
674
                     be truncated to end with revision_id.
506
675
        """
507
 
        # for API compatibility, until 0.8 releases we provide the old api:
508
 
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
509
 
        # after 0.8 releases, the *args and **kwargs should be changed:
510
 
        # def clone(self, to_bzrdir, revision_id=None):
511
 
        if (kwargs.get('to_location', None) or
512
 
            kwargs.get('revision', None) or
513
 
            kwargs.get('basis_branch', None) or
514
 
            (len(args) and isinstance(args[0], basestring))):
515
 
            # backwards compatibility api:
516
 
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
517
 
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
518
 
            # get basis_branch
519
 
            if len(args) > 2:
520
 
                basis_branch = args[2]
521
 
            else:
522
 
                basis_branch = kwargs.get('basis_branch', None)
523
 
            if basis_branch:
524
 
                basis = basis_branch.bzrdir
525
 
            else:
526
 
                basis = None
527
 
            # get revision
528
 
            if len(args) > 1:
529
 
                revision_id = args[1]
530
 
            else:
531
 
                revision_id = kwargs.get('revision', None)
532
 
            # get location
533
 
            if len(args):
534
 
                url = args[0]
535
 
            else:
536
 
                # no default to raise if not provided.
537
 
                url = kwargs.get('to_location')
538
 
            return self.bzrdir.clone(url,
539
 
                                     revision_id=revision_id,
540
 
                                     basis=basis).open_branch()
541
 
        # new cleaner api.
542
 
        # generate args by hand 
543
 
        if len(args) > 1:
544
 
            revision_id = args[1]
545
 
        else:
546
 
            revision_id = kwargs.get('revision_id', None)
547
 
        if len(args):
548
 
            to_bzrdir = args[0]
549
 
        else:
550
 
            # no default to raise if not provided.
551
 
            to_bzrdir = kwargs.get('to_bzrdir')
552
676
        result = self._format.initialize(to_bzrdir)
553
677
        self.copy_content_into(result, revision_id=revision_id)
554
678
        return  result
580
704
        """
581
705
        new_history = self.revision_history()
582
706
        if revision_id is not None:
 
707
            revision_id = osutils.safe_revision_id(revision_id)
583
708
            try:
584
709
                new_history = new_history[:new_history.index(revision_id) + 1]
585
710
            except ValueError:
602
727
        else:
603
728
            if parent:
604
729
                destination.set_parent(parent)
 
730
        self.tags.merge_to(destination.tags)
605
731
 
606
732
    @needs_read_lock
607
733
    def check(self):
635
761
 
636
762
    def _get_checkout_format(self):
637
763
        """Return the most suitable metadir for a checkout of this branch.
638
 
        Weaves are used if this branch's repostory uses weaves.
 
764
        Weaves are used if this branch's repository uses weaves.
639
765
        """
640
766
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
641
767
            from bzrlib.repofmt import weaverepo
642
768
            format = bzrdir.BzrDirMetaFormat1()
643
769
            format.repository_format = weaverepo.RepositoryFormat7()
644
770
        else:
645
 
            format = self.repository.bzrdir.cloning_metadir()
646
 
            format.branch_format = self._format
 
771
            format = self.repository.bzrdir.checkout_metadir()
 
772
            format.set_branch_format(self._format)
647
773
        return format
648
774
 
649
775
    def create_checkout(self, to_location, revision_id=None,
657
783
        :return: The tree of the created checkout
658
784
        """
659
785
        t = transport.get_transport(to_location)
660
 
        try:
661
 
            t.mkdir('.')
662
 
        except errors.FileExists:
663
 
            pass
 
786
        t.ensure_base()
664
787
        if lightweight:
665
 
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
788
            format = self._get_checkout_format()
 
789
            checkout = format.initialize_on_transport(t)
666
790
            BranchReferenceFormat().initialize(checkout, self)
667
791
        else:
668
792
            format = self._get_checkout_format()
673
797
            # pull up to the specified revision_id to set the initial 
674
798
            # branch tip correctly, and seed it with history.
675
799
            checkout_branch.pull(self, stop_revision=revision_id)
676
 
        return checkout.create_workingtree(revision_id)
 
800
        tree = checkout.create_workingtree(revision_id)
 
801
        basis_tree = tree.basis_tree()
 
802
        basis_tree.lock_read()
 
803
        try:
 
804
            for path, file_id in basis_tree.iter_references():
 
805
                reference_parent = self.reference_parent(file_id, path)
 
806
                reference_parent.create_checkout(tree.abspath(path),
 
807
                    basis_tree.get_reference_revision(file_id, path),
 
808
                    lightweight)
 
809
        finally:
 
810
            basis_tree.unlock()
 
811
        return tree
 
812
 
 
813
    def reference_parent(self, file_id, path):
 
814
        """Return the parent branch for a tree-reference file_id
 
815
        :param file_id: The file_id of the tree reference
 
816
        :param path: The path of the file_id in the tree
 
817
        :return: A branch associated with the file_id
 
818
        """
 
819
        # FIXME should provide multiple branches, based on config
 
820
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
 
821
 
 
822
    def supports_tags(self):
 
823
        return self._format.supports_tags()
677
824
 
678
825
 
679
826
class BranchFormat(object):
700
847
    _formats = {}
701
848
    """The known formats."""
702
849
 
 
850
    def __eq__(self, other):
 
851
        return self.__class__ is other.__class__
 
852
 
 
853
    def __ne__(self, other):
 
854
        return not (self == other)
 
855
 
703
856
    @classmethod
704
857
    def find_format(klass, a_bzrdir):
705
858
        """Return the format for the branch object in a_bzrdir."""
717
870
        """Return the current default format."""
718
871
        return klass._default_format
719
872
 
 
873
    def get_reference(self, a_bzrdir):
 
874
        """Get the target reference of the branch in a_bzrdir.
 
875
 
 
876
        format probing must have been completed before calling
 
877
        this method - it is assumed that the format of the branch
 
878
        in a_bzrdir is correct.
 
879
 
 
880
        :param a_bzrdir: The bzrdir to get the branch data from.
 
881
        :return: None if the branch is not a reference branch.
 
882
        """
 
883
        return None
 
884
 
720
885
    def get_format_string(self):
721
886
        """Return the ASCII format string that identifies this format."""
722
887
        raise NotImplementedError(self.get_format_string)
794
959
    def __str__(self):
795
960
        return self.get_format_string().rstrip()
796
961
 
797
 
 
798
 
class BranchHooks(dict):
 
962
    def supports_tags(self):
 
963
        """True if this format supports tags stored in the branch"""
 
964
        return False  # by default
 
965
 
 
966
    # XXX: Probably doesn't really belong here -- mbp 20070212
 
967
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
968
            lock_class):
 
969
        branch_transport = a_bzrdir.get_branch_transport(self)
 
970
        control_files = lockable_files.LockableFiles(branch_transport,
 
971
            lock_filename, lock_class)
 
972
        control_files.create_lock()
 
973
        control_files.lock_write()
 
974
        try:
 
975
            for filename, content in utf8_files:
 
976
                control_files.put_utf8(filename, content)
 
977
        finally:
 
978
            control_files.unlock()
 
979
 
 
980
 
 
981
class BranchHooks(Hooks):
799
982
    """A dictionary mapping hook name to a list of callables for branch hooks.
800
983
    
801
984
    e.g. ['set_rh'] Is the list of items to be called when the
808
991
        These are all empty initially, because by default nothing should get
809
992
        notified.
810
993
        """
811
 
        dict.__init__(self)
 
994
        Hooks.__init__(self)
812
995
        # Introduced in 0.15:
813
996
        # invoked whenever the revision history has been set
814
997
        # with set_revision_history. The api signature is
817
1000
        self['set_rh'] = []
818
1001
        # invoked after a push operation completes.
819
1002
        # the api signature is
 
1003
        # (push_result)
 
1004
        # containing the members
820
1005
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
821
 
        # where local is the local branch or None, master is the target 
 
1006
        # where local is the local target branch or None, master is the target 
822
1007
        # master branch, and the rest should be self explanatory. The source
823
1008
        # is read locked and the target branches write locked. Source will
824
1009
        # be the local low-latency branch.
825
1010
        self['post_push'] = []
826
1011
        # invoked after a pull operation completes.
827
1012
        # the api signature is
 
1013
        # (pull_result)
 
1014
        # containing the members
828
1015
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
829
1016
        # where local is the local branch or None, master is the target 
830
1017
        # master branch, and the rest should be self explanatory. The source
843
1030
        # and an empty branch recieves new_revno of 0, new_revid of None.
844
1031
        self['post_uncommit'] = []
845
1032
 
846
 
    def install_hook(self, hook_name, a_callable):
847
 
        """Install a_callable in to the hook hook_name.
848
 
 
849
 
        :param hook_name: A hook name. See the __init__ method of BranchHooks
850
 
            for the complete list of hooks.
851
 
        :param a_callable: The callable to be invoked when the hook triggers.
852
 
            The exact signature will depend on the hook - see the __init__ 
853
 
            method of BranchHooks for details on each hook.
854
 
        """
855
 
        try:
856
 
            self[hook_name].append(a_callable)
857
 
        except KeyError:
858
 
            raise errors.UnknownHook('branch', hook_name)
859
 
 
860
1033
 
861
1034
# install the default hooks into the Branch class.
862
1035
Branch.hooks = BranchHooks()
945
1118
        if not _found:
946
1119
            format = BranchFormat.find_format(a_bzrdir)
947
1120
            assert format.__class__ == self.__class__
948
 
        transport = a_bzrdir.get_branch_transport(None)
949
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
950
 
                                                     lockdir.LockDir)
951
 
        return BzrBranch5(_format=self,
952
 
                          _control_files=control_files,
953
 
                          a_bzrdir=a_bzrdir,
954
 
                          _repository=a_bzrdir.find_repository())
955
 
 
956
 
    def __str__(self):
957
 
        return "Bazaar-NG Metadir branch format 5"
 
1121
        try:
 
1122
            transport = a_bzrdir.get_branch_transport(None)
 
1123
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
1124
                                                         lockdir.LockDir)
 
1125
            return BzrBranch5(_format=self,
 
1126
                              _control_files=control_files,
 
1127
                              a_bzrdir=a_bzrdir,
 
1128
                              _repository=a_bzrdir.find_repository())
 
1129
        except NoSuchFile:
 
1130
            raise NotBranchError(path=transport.base)
958
1131
 
959
1132
 
960
1133
class BzrBranchFormat6(BzrBranchFormat5):
969
1142
 
970
1143
    def get_format_string(self):
971
1144
        """See BranchFormat.get_format_string()."""
972
 
        return "Bazaar-NG branch format 6\n"
 
1145
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
973
1146
 
974
1147
    def get_format_description(self):
975
1148
        """See BranchFormat.get_format_description()."""
979
1152
        """Create a branch of this format in a_bzrdir."""
980
1153
        utf8_files = [('last-revision', '0 null:\n'),
981
1154
                      ('branch-name', ''),
982
 
                      ('branch.conf', '')
 
1155
                      ('branch.conf', ''),
 
1156
                      ('tags', ''),
983
1157
                      ]
984
1158
        return self._initialize_helper(a_bzrdir, utf8_files)
985
1159
 
1000
1174
                          a_bzrdir=a_bzrdir,
1001
1175
                          _repository=a_bzrdir.find_repository())
1002
1176
 
 
1177
    def supports_tags(self):
 
1178
        return True
 
1179
 
1003
1180
 
1004
1181
class BranchReferenceFormat(BranchFormat):
1005
1182
    """Bzr branch reference format.
1020
1197
        """See BranchFormat.get_format_description()."""
1021
1198
        return "Checkout reference format 1"
1022
1199
        
 
1200
    def get_reference(self, a_bzrdir):
 
1201
        """See BranchFormat.get_reference()."""
 
1202
        transport = a_bzrdir.get_branch_transport(None)
 
1203
        return transport.get('location').read()
 
1204
 
1023
1205
    def initialize(self, a_bzrdir, target_branch=None):
1024
1206
        """Create a branch of this format in a_bzrdir."""
1025
1207
        if target_branch is None:
1047
1229
            # emit some sort of warning/error to the caller ?!
1048
1230
        return clone
1049
1231
 
1050
 
    def open(self, a_bzrdir, _found=False):
 
1232
    def open(self, a_bzrdir, _found=False, location=None):
1051
1233
        """Return the branch that the branch reference in a_bzrdir points at.
1052
1234
 
1053
1235
        _found is a private parameter, do not use it. It is used to indicate
1056
1238
        if not _found:
1057
1239
            format = BranchFormat.find_format(a_bzrdir)
1058
1240
            assert format.__class__ == self.__class__
1059
 
        transport = a_bzrdir.get_branch_transport(None)
1060
 
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
1241
        if location is None:
 
1242
            location = self.get_reference(a_bzrdir)
 
1243
        real_bzrdir = bzrdir.BzrDir.open(location)
1061
1244
        result = real_bzrdir.open_branch()
1062
1245
        # this changes the behaviour of result.clone to create a new reference
1063
1246
        # rather than a copy of the content of the branch.
1089
1272
    it's writable, and can be accessed via the normal filesystem API.
1090
1273
    """
1091
1274
    
1092
 
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
1093
 
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
1275
    def __init__(self, _format=None,
1094
1276
                 _control_files=None, a_bzrdir=None, _repository=None):
1095
 
        """Create new branch object at a particular location.
1096
 
 
1097
 
        transport -- A Transport object, defining how to access files.
1098
 
        
1099
 
        init -- If True, create new control files in a previously
1100
 
             unversioned directory.  If False, the branch must already
1101
 
             be versioned.
1102
 
 
1103
 
        relax_version_check -- If true, the usual check for the branch
1104
 
            version is not applied.  This is intended only for
1105
 
            upgrade/recovery type use; it's not guaranteed that
1106
 
            all operations will work on old format branches.
1107
 
        """
 
1277
        """Create new branch object at a particular location."""
 
1278
        Branch.__init__(self)
1108
1279
        if a_bzrdir is None:
1109
 
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
1280
            raise ValueError('a_bzrdir must be supplied')
1110
1281
        else:
1111
1282
            self.bzrdir = a_bzrdir
1112
 
        self._transport = self.bzrdir.transport.clone('..')
1113
 
        self._base = self._transport.base
 
1283
        # self._transport used to point to the directory containing the
 
1284
        # control directory, but was not used - now it's just the transport
 
1285
        # for the branch control files.  mbp 20070212
 
1286
        self._base = self.bzrdir.transport.clone('..').base
1114
1287
        self._format = _format
1115
1288
        if _control_files is None:
1116
1289
            raise ValueError('BzrBranch _control_files is None')
1117
1290
        self.control_files = _control_files
1118
 
        if deprecated_passed(init):
1119
 
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1120
 
                 "deprecated as of bzr 0.8. Please use Branch.create().",
1121
 
                 DeprecationWarning,
1122
 
                 stacklevel=2)
1123
 
            if init:
1124
 
                # this is slower than before deprecation, oh well never mind.
1125
 
                # -> its deprecated.
1126
 
                self._initialize(transport.base)
1127
 
        self._check_format(_format)
1128
 
        if deprecated_passed(relax_version_check):
1129
 
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1130
 
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
1131
 
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1132
 
                 "open() method.",
1133
 
                 DeprecationWarning,
1134
 
                 stacklevel=2)
1135
 
            if (not relax_version_check
1136
 
                and not self._format.is_supported()):
1137
 
                raise errors.UnsupportedFormatError(format=fmt)
1138
 
        if deprecated_passed(transport):
1139
 
            warn("BzrBranch.__init__(transport=XXX...): The transport "
1140
 
                 "parameter is deprecated as of bzr 0.8. "
1141
 
                 "Please use Branch.open, or bzrdir.open_branch().",
1142
 
                 DeprecationWarning,
1143
 
                 stacklevel=2)
 
1291
        self._transport = _control_files._transport
1144
1292
        self.repository = _repository
1145
1293
 
1146
1294
    def __str__(self):
1149
1297
    __repr__ = __str__
1150
1298
 
1151
1299
    def _get_base(self):
 
1300
        """Returns the directory containing the control directory."""
1152
1301
        return self._base
1153
1302
 
1154
1303
    base = property(_get_base, doc="The URL for the root of this branch.")
1155
1304
 
1156
 
    def _finish_transaction(self):
1157
 
        """Exit the current transaction."""
1158
 
        return self.control_files._finish_transaction()
1159
 
 
1160
 
    def get_transaction(self):
1161
 
        """Return the current active transaction.
1162
 
 
1163
 
        If no transaction is active, this returns a passthrough object
1164
 
        for which all data is immediately flushed and no caching happens.
1165
 
        """
1166
 
        # this is an explicit function so that we can do tricky stuff
1167
 
        # when the storage in rev_storage is elsewhere.
1168
 
        # we probably need to hook the two 'lock a location' and 
1169
 
        # 'have a transaction' together more delicately, so that
1170
 
        # we can have two locks (branch and storage) and one transaction
1171
 
        # ... and finishing the transaction unlocks both, but unlocking
1172
 
        # does not. - RBC 20051121
1173
 
        return self.control_files.get_transaction()
1174
 
 
1175
 
    def _set_transaction(self, transaction):
1176
 
        """Set a new active transaction."""
1177
 
        return self.control_files._set_transaction(transaction)
1178
 
 
1179
1305
    def abspath(self, name):
1180
1306
        """See Branch.abspath."""
1181
1307
        return self.control_files._transport.abspath(name)
1182
1308
 
1183
 
    def _check_format(self, format):
1184
 
        """Identify the branch format if needed.
1185
 
 
1186
 
        The format is stored as a reference to the format object in
1187
 
        self._format for code that needs to check it later.
1188
 
 
1189
 
        The format parameter is either None or the branch format class
1190
 
        used to open this branch.
1191
 
 
1192
 
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
1193
 
        """
1194
 
        if format is None:
1195
 
            format = BranchFormat.find_format(self.bzrdir)
1196
 
        self._format = format
1197
 
        mutter("got branch format %s", self._format)
1198
 
 
 
1309
 
 
1310
    @deprecated_method(zero_sixteen)
1199
1311
    @needs_read_lock
1200
1312
    def get_root_id(self):
1201
1313
        """See Branch.get_root_id."""
1205
1317
    def is_locked(self):
1206
1318
        return self.control_files.is_locked()
1207
1319
 
1208
 
    def lock_write(self):
1209
 
        self.repository.lock_write()
 
1320
    def lock_write(self, token=None):
 
1321
        repo_token = self.repository.lock_write()
1210
1322
        try:
1211
 
            self.control_files.lock_write()
 
1323
            token = self.control_files.lock_write(token=token)
1212
1324
        except:
1213
1325
            self.repository.unlock()
1214
1326
            raise
 
1327
        return token
1215
1328
 
1216
1329
    def lock_read(self):
1217
1330
        self.repository.lock_read()
1227
1340
            self.control_files.unlock()
1228
1341
        finally:
1229
1342
            self.repository.unlock()
 
1343
        if not self.control_files.is_locked():
 
1344
            # we just released the lock
 
1345
            self._clear_cached_state()
1230
1346
        
1231
1347
    def peek_lock_mode(self):
1232
1348
        if self.control_files._lock_count == 0:
1245
1361
    @needs_write_lock
1246
1362
    def append_revision(self, *revision_ids):
1247
1363
        """See Branch.append_revision."""
 
1364
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1248
1365
        for revision_id in revision_ids:
1249
1366
            _mod_revision.check_not_reserved_id(revision_id)
1250
1367
            mutter("add {%s} to revision-history" % revision_id)
1257
1374
 
1258
1375
        This performs the actual writing to disk.
1259
1376
        It is intended to be called by BzrBranch5.set_revision_history."""
1260
 
        self.control_files.put_utf8(
 
1377
        self.control_files.put_bytes(
1261
1378
            'revision-history', '\n'.join(history))
1262
1379
 
1263
1380
    @needs_write_lock
1264
1381
    def set_revision_history(self, rev_history):
1265
1382
        """See Branch.set_revision_history."""
 
1383
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
 
1384
        self._clear_cached_state()
1266
1385
        self._write_revision_history(rev_history)
1267
 
        transaction = self.get_transaction()
1268
 
        history = transaction.map.find_revision_history()
1269
 
        if history is not None:
1270
 
            # update the revision history in the identity map.
1271
 
            history[:] = list(rev_history)
1272
 
            # this call is disabled because revision_history is 
1273
 
            # not really an object yet, and the transaction is for objects.
1274
 
            # transaction.register_dirty(history)
1275
 
        else:
1276
 
            transaction.map.add_revision_history(rev_history)
1277
 
            # this call is disabled because revision_history is 
1278
 
            # not really an object yet, and the transaction is for objects.
1279
 
            # transaction.register_clean(history)
 
1386
        self._cache_revision_history(rev_history)
1280
1387
        for hook in Branch.hooks['set_rh']:
1281
1388
            hook(self, rev_history)
1282
1389
 
1283
1390
    @needs_write_lock
1284
1391
    def set_last_revision_info(self, revno, revision_id):
 
1392
        revision_id = osutils.safe_revision_id(revision_id)
1285
1393
        history = self._lefthand_history(revision_id)
1286
1394
        assert len(history) == revno, '%d != %d' % (len(history), revno)
1287
1395
        self.set_revision_history(history)
1288
1396
 
1289
1397
    def _gen_revision_history(self):
1290
 
        decode_utf8 = cache_utf8.decode
1291
 
        history = [decode_utf8(l.rstrip('\r\n')) for l in
1292
 
                self.control_files.get('revision-history').readlines()]
 
1398
        history = self.control_files.get('revision-history').read().split('\n')
 
1399
        if history[-1:] == ['']:
 
1400
            # There shouldn't be a trailing newline, but just in case.
 
1401
            history.pop()
1293
1402
        return history
1294
1403
 
1295
 
    @needs_read_lock
1296
 
    def revision_history(self):
1297
 
        """See Branch.revision_history."""
1298
 
        transaction = self.get_transaction()
1299
 
        history = transaction.map.find_revision_history()
1300
 
        if history is not None:
1301
 
            # mutter("cache hit for revision-history in %s", self)
1302
 
            return list(history)
1303
 
        history = self._gen_revision_history()
1304
 
        transaction.map.add_revision_history(history)
1305
 
        # this call is disabled because revision_history is 
1306
 
        # not really an object yet, and the transaction is for objects.
1307
 
        # transaction.register_clean(history, precious=True)
1308
 
        return list(history)
1309
 
 
1310
1404
    def _lefthand_history(self, revision_id, last_rev=None,
1311
1405
                          other_branch=None):
1312
1406
        # stop_revision must be a descendant of last_revision
1338
1432
        :param other_branch: The other branch that DivergedBranches should
1339
1433
            raise with respect to.
1340
1434
        """
 
1435
        revision_id = osutils.safe_revision_id(revision_id)
1341
1436
        self.set_revision_history(self._lefthand_history(revision_id,
1342
1437
            last_rev, other_branch))
1343
1438
 
1351
1446
                if stop_revision is None:
1352
1447
                    # if there are no commits, we're done.
1353
1448
                    return
 
1449
            else:
 
1450
                stop_revision = osutils.safe_revision_id(stop_revision)
1354
1451
            # whats the current last revision, before we fetch [and change it
1355
1452
            # possibly]
1356
1453
            last_rev = self.last_revision()
1357
1454
            # we fetch here regardless of whether we need to so that we pickup
1358
1455
            # filled in ghosts.
1359
1456
            self.fetch(other, stop_revision)
1360
 
            my_ancestry = self.repository.get_ancestry(last_rev)
 
1457
            my_ancestry = self.repository.get_ancestry(last_rev,
 
1458
                                                       topo_sorted=False)
1361
1459
            if stop_revision in my_ancestry:
1362
1460
                # last_revision is a descendant of stop_revision
1363
1461
                return
1382
1480
 
1383
1481
    @needs_write_lock
1384
1482
    def pull(self, source, overwrite=False, stop_revision=None,
1385
 
        _hook_master=None, _run_hooks=True):
 
1483
             _hook_master=None, run_hooks=True):
1386
1484
        """See Branch.pull.
1387
1485
 
1388
1486
        :param _hook_master: Private parameter - set the branch to 
1389
1487
            be supplied as the master to push hooks.
1390
 
        :param _run_hooks: Private parameter - allow disabling of
1391
 
            hooks, used when pushing to a master branch.
 
1488
        :param run_hooks: Private parameter - if false, this branch
 
1489
            is being called because it's the master of the primary branch,
 
1490
            so it should not run its hooks.
1392
1491
        """
 
1492
        result = PullResult()
 
1493
        result.source_branch = source
 
1494
        result.target_branch = self
1393
1495
        source.lock_read()
1394
1496
        try:
1395
 
            old_count, old_tip = self.last_revision_info()
 
1497
            result.old_revno, result.old_revid = self.last_revision_info()
1396
1498
            try:
1397
1499
                self.update_revisions(source, stop_revision)
1398
1500
            except DivergedBranches:
1399
1501
                if not overwrite:
1400
1502
                    raise
1401
1503
            if overwrite:
1402
 
                self.set_revision_history(source.revision_history())
1403
 
            new_count, new_tip = self.last_revision_info()
1404
 
            if _run_hooks:
1405
 
                if _hook_master:
1406
 
                    _hook_local = self
1407
 
                else:
1408
 
                    _hook_master = self
1409
 
                    _hook_local = None
 
1504
                if stop_revision is None:
 
1505
                    stop_revision = source.last_revision()
 
1506
                self.generate_revision_history(stop_revision)
 
1507
            result.tag_conflicts = source.tags.merge_to(self.tags)
 
1508
            result.new_revno, result.new_revid = self.last_revision_info()
 
1509
            if _hook_master:
 
1510
                result.master_branch = _hook_master
 
1511
                result.local_branch = self
 
1512
            else:
 
1513
                result.master_branch = self
 
1514
                result.local_branch = None
 
1515
            if run_hooks:
1410
1516
                for hook in Branch.hooks['post_pull']:
1411
 
                    hook(source, _hook_local, _hook_master, old_count, old_tip,
1412
 
                        new_count, new_tip)
1413
 
            return new_count - old_count
 
1517
                    hook(result)
1414
1518
        finally:
1415
1519
            source.unlock()
 
1520
        return result
1416
1521
 
1417
1522
    def _get_parent_location(self):
1418
1523
        _locs = ['parent', 'pull', 'x-pull']
1425
1530
 
1426
1531
    @needs_read_lock
1427
1532
    def push(self, target, overwrite=False, stop_revision=None,
1428
 
        _hook_master=None, _run_hooks=True):
 
1533
             _override_hook_source_branch=None):
1429
1534
        """See Branch.push.
1430
 
        
1431
 
        :param _hook_master: Private parameter - set the branch to 
1432
 
            be supplied as the master to push hooks.
1433
 
        :param _run_hooks: Private parameter - allow disabling of
1434
 
            hooks, used when pushing to a master branch.
 
1535
 
 
1536
        This is the basic concrete implementation of push()
 
1537
 
 
1538
        :param _override_hook_source_branch: If specified, run
 
1539
        the hooks passing this Branch as the source, rather than self.  
 
1540
        This is for use of RemoteBranch, where push is delegated to the
 
1541
        underlying vfs-based Branch. 
1435
1542
        """
 
1543
        # TODO: Public option to disable running hooks - should be trivial but
 
1544
        # needs tests.
1436
1545
        target.lock_write()
1437
1546
        try:
1438
 
            old_count, old_tip = target.last_revision_info()
1439
 
            try:
1440
 
                target.update_revisions(self, stop_revision)
1441
 
            except DivergedBranches:
1442
 
                if not overwrite:
1443
 
                    raise
1444
 
            if overwrite:
1445
 
                target.set_revision_history(self.revision_history())
1446
 
            new_count, new_tip = target.last_revision_info()
1447
 
            if _run_hooks:
1448
 
                if _hook_master:
1449
 
                    _hook_local = target
1450
 
                else:
1451
 
                    _hook_master = target
1452
 
                    _hook_local = None
1453
 
                for hook in Branch.hooks['post_push']:
1454
 
                    hook(self, _hook_local, _hook_master, old_count, old_tip,
1455
 
                        new_count, new_tip)
1456
 
            return new_count - old_count
 
1547
            result = self._push_with_bound_branches(target, overwrite,
 
1548
                    stop_revision,
 
1549
                    _override_hook_source_branch=_override_hook_source_branch)
 
1550
            return result
1457
1551
        finally:
1458
1552
            target.unlock()
1459
1553
 
 
1554
    def _push_with_bound_branches(self, target, overwrite,
 
1555
            stop_revision,
 
1556
            _override_hook_source_branch=None):
 
1557
        """Push from self into target, and into target's master if any.
 
1558
        
 
1559
        This is on the base BzrBranch class even though it doesn't support 
 
1560
        bound branches because the *target* might be bound.
 
1561
        """
 
1562
        def _run_hooks():
 
1563
            if _override_hook_source_branch:
 
1564
                result.source_branch = _override_hook_source_branch
 
1565
            for hook in Branch.hooks['post_push']:
 
1566
                hook(result)
 
1567
 
 
1568
        bound_location = target.get_bound_location()
 
1569
        if bound_location and target.base != bound_location:
 
1570
            # there is a master branch.
 
1571
            #
 
1572
            # XXX: Why the second check?  Is it even supported for a branch to
 
1573
            # be bound to itself? -- mbp 20070507
 
1574
            master_branch = target.get_master_branch()
 
1575
            master_branch.lock_write()
 
1576
            try:
 
1577
                # push into the master from this branch.
 
1578
                self._basic_push(master_branch, overwrite, stop_revision)
 
1579
                # and push into the target branch from this. Note that we push from
 
1580
                # this branch again, because its considered the highest bandwidth
 
1581
                # repository.
 
1582
                result = self._basic_push(target, overwrite, stop_revision)
 
1583
                result.master_branch = master_branch
 
1584
                result.local_branch = target
 
1585
                _run_hooks()
 
1586
                return result
 
1587
            finally:
 
1588
                master_branch.unlock()
 
1589
        else:
 
1590
            # no master branch
 
1591
            result = self._basic_push(target, overwrite, stop_revision)
 
1592
            # TODO: Why set master_branch and local_branch if there's no
 
1593
            # binding?  Maybe cleaner to just leave them unset? -- mbp
 
1594
            # 20070504
 
1595
            result.master_branch = target
 
1596
            result.local_branch = None
 
1597
            _run_hooks()
 
1598
            return result
 
1599
 
 
1600
    def _basic_push(self, target, overwrite, stop_revision):
 
1601
        """Basic implementation of push without bound branches or hooks.
 
1602
 
 
1603
        Must be called with self read locked and target write locked.
 
1604
        """
 
1605
        result = PushResult()
 
1606
        result.source_branch = self
 
1607
        result.target_branch = target
 
1608
        result.old_revno, result.old_revid = target.last_revision_info()
 
1609
        try:
 
1610
            target.update_revisions(self, stop_revision)
 
1611
        except DivergedBranches:
 
1612
            if not overwrite:
 
1613
                raise
 
1614
        if overwrite:
 
1615
            target.set_revision_history(self.revision_history())
 
1616
        result.tag_conflicts = self.tags.merge_to(target.tags)
 
1617
        result.new_revno, result.new_revid = target.last_revision_info()
 
1618
        return result
 
1619
 
1460
1620
    def get_parent(self):
1461
1621
        """See Branch.get_parent."""
1462
1622
 
1473
1633
        except errors.InvalidURLJoin, e:
1474
1634
            raise errors.InaccessibleParent(parent, self.base)
1475
1635
 
1476
 
    def get_push_location(self):
1477
 
        """See Branch.get_push_location."""
1478
 
        push_loc = self.get_config().get_user_option('push_location')
1479
 
        return push_loc
1480
 
 
1481
1636
    def set_push_location(self, location):
1482
1637
        """See Branch.set_push_location."""
1483
1638
        self.get_config().set_user_option(
1497
1652
                try: 
1498
1653
                    url = url.encode('ascii')
1499
1654
                except UnicodeEncodeError:
1500
 
                    raise bzrlib.errors.InvalidURL(url,
 
1655
                    raise errors.InvalidURL(url,
1501
1656
                        "Urls must be 7-bit ascii, "
1502
1657
                        "use bzrlib.urlutils.escape")
1503
 
                    
1504
1658
            url = urlutils.relative_url(self.base, url)
1505
1659
        self._set_parent_location(url)
1506
1660
 
1509
1663
            self.control_files._transport.delete('parent')
1510
1664
        else:
1511
1665
            assert isinstance(url, str)
1512
 
            self.control_files.put('parent', StringIO(url + '\n'))
 
1666
            self.control_files.put_bytes('parent', url + '\n')
1513
1667
 
1514
1668
    @deprecated_function(zero_nine)
1515
1669
    def tree_config(self):
1536
1690
        
1537
1691
    @needs_write_lock
1538
1692
    def pull(self, source, overwrite=False, stop_revision=None,
1539
 
        _run_hooks=True):
1540
 
        """Extends branch.pull to be bound branch aware.
 
1693
             run_hooks=True):
 
1694
        """Pull from source into self, updating my master if any.
1541
1695
        
1542
 
        :param _run_hooks: Private parameter used to force hook running
1543
 
            off during bound branch double-pushing.
 
1696
        :param run_hooks: Private parameter - if false, this branch
 
1697
            is being called because it's the master of the primary branch,
 
1698
            so it should not run its hooks.
1544
1699
        """
1545
1700
        bound_location = self.get_bound_location()
1546
1701
        master_branch = None
1552
1707
            if master_branch:
1553
1708
                # pull from source into master.
1554
1709
                master_branch.pull(source, overwrite, stop_revision,
1555
 
                    _run_hooks=False)
 
1710
                    run_hooks=False)
1556
1711
            return super(BzrBranch5, self).pull(source, overwrite,
1557
1712
                stop_revision, _hook_master=master_branch,
1558
 
                _run_hooks=_run_hooks)
1559
 
        finally:
1560
 
            if master_branch:
1561
 
                master_branch.unlock()
1562
 
 
1563
 
    @needs_read_lock
1564
 
    def push(self, target, overwrite=False, stop_revision=None):
1565
 
        """Updates branch.push to be bound branch aware."""
1566
 
        bound_location = target.get_bound_location()
1567
 
        master_branch = None
1568
 
        if bound_location and target.base != bound_location:
1569
 
            # not pushing to master, so we need to update master.
1570
 
            master_branch = target.get_master_branch()
1571
 
            master_branch.lock_write()
1572
 
        try:
1573
 
            if master_branch:
1574
 
                # push into the master from this branch.
1575
 
                super(BzrBranch5, self).push(master_branch, overwrite,
1576
 
                    stop_revision, _run_hooks=False)
1577
 
            # and push into the target branch from this. Note that we push from
1578
 
            # this branch again, because its considered the highest bandwidth
1579
 
            # repository.
1580
 
            return super(BzrBranch5, self).push(target, overwrite,
1581
 
                stop_revision, _hook_master=master_branch)
 
1713
                run_hooks=run_hooks)
1582
1714
        finally:
1583
1715
            if master_branch:
1584
1716
                master_branch.unlock()
1682
1814
        if master is not None:
1683
1815
            old_tip = self.last_revision()
1684
1816
            self.pull(master, overwrite=True)
1685
 
            if old_tip in self.repository.get_ancestry(self.last_revision()):
 
1817
            if old_tip in self.repository.get_ancestry(self.last_revision(),
 
1818
                                                       topo_sorted=False):
1686
1819
                return None
1687
1820
            return old_tip
1688
1821
        return None
1689
1822
 
1690
1823
 
 
1824
class BzrBranchExperimental(BzrBranch5):
 
1825
    """Bzr experimental branch format
 
1826
 
 
1827
    This format has:
 
1828
     - a revision-history file.
 
1829
     - a format string
 
1830
     - a lock dir guarding the branch itself
 
1831
     - all of this stored in a branch/ subdirectory
 
1832
     - works with shared repositories.
 
1833
     - a tag dictionary in the branch
 
1834
 
 
1835
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
1836
    only for testing.
 
1837
 
 
1838
    This class acts as it's own BranchFormat.
 
1839
    """
 
1840
 
 
1841
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1842
 
 
1843
    @classmethod
 
1844
    def get_format_string(cls):
 
1845
        """See BranchFormat.get_format_string()."""
 
1846
        return "Bazaar-NG branch format experimental\n"
 
1847
 
 
1848
    @classmethod
 
1849
    def get_format_description(cls):
 
1850
        """See BranchFormat.get_format_description()."""
 
1851
        return "Experimental branch format"
 
1852
 
 
1853
    @classmethod
 
1854
    def get_reference(cls, a_bzrdir):
 
1855
        """Get the target reference of the branch in a_bzrdir.
 
1856
 
 
1857
        format probing must have been completed before calling
 
1858
        this method - it is assumed that the format of the branch
 
1859
        in a_bzrdir is correct.
 
1860
 
 
1861
        :param a_bzrdir: The bzrdir to get the branch data from.
 
1862
        :return: None if the branch is not a reference branch.
 
1863
        """
 
1864
        return None
 
1865
 
 
1866
    @classmethod
 
1867
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
 
1868
            lock_class):
 
1869
        branch_transport = a_bzrdir.get_branch_transport(cls)
 
1870
        control_files = lockable_files.LockableFiles(branch_transport,
 
1871
            lock_filename, lock_class)
 
1872
        control_files.create_lock()
 
1873
        control_files.lock_write()
 
1874
        try:
 
1875
            for filename, content in utf8_files:
 
1876
                control_files.put_utf8(filename, content)
 
1877
        finally:
 
1878
            control_files.unlock()
 
1879
        
 
1880
    @classmethod
 
1881
    def initialize(cls, a_bzrdir):
 
1882
        """Create a branch of this format in a_bzrdir."""
 
1883
        utf8_files = [('format', cls.get_format_string()),
 
1884
                      ('revision-history', ''),
 
1885
                      ('branch-name', ''),
 
1886
                      ('tags', ''),
 
1887
                      ]
 
1888
        cls._initialize_control_files(a_bzrdir, utf8_files,
 
1889
            'lock', lockdir.LockDir)
 
1890
        return cls.open(a_bzrdir, _found=True)
 
1891
 
 
1892
    @classmethod
 
1893
    def open(cls, a_bzrdir, _found=False):
 
1894
        """Return the branch object for a_bzrdir
 
1895
 
 
1896
        _found is a private parameter, do not use it. It is used to indicate
 
1897
               if format probing has already be done.
 
1898
        """
 
1899
        if not _found:
 
1900
            format = BranchFormat.find_format(a_bzrdir)
 
1901
            assert format.__class__ == cls
 
1902
        transport = a_bzrdir.get_branch_transport(None)
 
1903
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1904
                                                     lockdir.LockDir)
 
1905
        return cls(_format=cls,
 
1906
            _control_files=control_files,
 
1907
            a_bzrdir=a_bzrdir,
 
1908
            _repository=a_bzrdir.find_repository())
 
1909
 
 
1910
    @classmethod
 
1911
    def is_supported(cls):
 
1912
        return True
 
1913
 
 
1914
    def _make_tags(self):
 
1915
        return BasicTags(self)
 
1916
 
 
1917
    @classmethod
 
1918
    def supports_tags(cls):
 
1919
        return True
 
1920
 
 
1921
 
 
1922
BranchFormat.register_format(BzrBranchExperimental)
 
1923
 
 
1924
 
1691
1925
class BzrBranch6(BzrBranch5):
1692
1926
 
1693
1927
    @needs_read_lock
1694
1928
    def last_revision_info(self):
1695
 
        revision_string = self.control_files.get_utf8('last-revision').read()
 
1929
        revision_string = self.control_files.get('last-revision').read()
1696
1930
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
 
1931
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1697
1932
        revno = int(revno)
1698
1933
        return revno, revision_id
1699
1934
 
1716
1951
        if revision_id is None:
1717
1952
            revision_id = 'null:'
1718
1953
        out_string = '%d %s\n' % (revno, revision_id)
1719
 
        self.control_files.put_utf8('last-revision', out_string)
 
1954
        self.control_files.put_bytes('last-revision', out_string)
1720
1955
 
1721
1956
    @needs_write_lock
1722
1957
    def set_last_revision_info(self, revno, revision_id):
 
1958
        revision_id = osutils.safe_revision_id(revision_id)
1723
1959
        if self._get_append_revisions_only():
1724
1960
            self._check_history_violation(revision_id)
1725
1961
        self._write_last_revision_info(revno, revision_id)
1726
 
        transaction = self.get_transaction()
1727
 
        cached_history = transaction.map.find_revision_history()
1728
 
        if cached_history is not None:
1729
 
            transaction.map.remove_object(cached_history)
 
1962
        self._clear_cached_state()
1730
1963
 
1731
1964
    def _check_history_violation(self, revision_id):
1732
1965
        last_revision = self.last_revision()
1761
1994
 
1762
1995
    @needs_write_lock
1763
1996
    def append_revision(self, *revision_ids):
 
1997
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1764
1998
        if len(revision_ids) == 0:
1765
1999
            return
1766
2000
        prev_revno, prev_revision = self.last_revision_info()
1777
2011
        self.set_last_revision_info(prev_revno + len(revision_ids),
1778
2012
                                    revision_ids[-1])
1779
2013
 
1780
 
    def _set_config_location(self, name, url, config=None,
1781
 
                             make_relative=False):
1782
 
        if config is None:
1783
 
            config = self.get_config()
1784
 
        if url is None:
1785
 
            url = ''
1786
 
        elif make_relative:
1787
 
            url = urlutils.relative_url(self.base, url)
1788
 
        config.set_user_option(name, url)
1789
 
 
1790
 
 
1791
 
    def _get_config_location(self, name, config=None):
1792
 
        if config is None:
1793
 
            config = self.get_config()
1794
 
        location = config.get_user_option(name)
1795
 
        if location == '':
1796
 
            location = None
1797
 
        return location
1798
 
 
1799
2014
    @needs_write_lock
1800
2015
    def _set_parent_location(self, url):
1801
2016
        """Set the parent branch"""
1870
2085
        if revision_id is None:
1871
2086
            revno, revision_id = self.last_revision_info()
1872
2087
        else:
1873
 
            revno = self.revision_id_to_revno(revision_id)
 
2088
            # To figure out the revno for a random revision, we need to build
 
2089
            # the revision history, and count its length.
 
2090
            # We don't care about the order, just how long it is.
 
2091
            # Alternatively, we could start at the current location, and count
 
2092
            # backwards. But there is no guarantee that we will find it since
 
2093
            # it may be a merged revision.
 
2094
            revno = len(list(self.repository.iter_reverse_revision_history(
 
2095
                                                                revision_id)))
1874
2096
        destination.set_last_revision_info(revno, revision_id)
1875
2097
 
 
2098
    def _make_tags(self):
 
2099
        return BasicTags(self)
 
2100
 
1876
2101
 
1877
2102
class BranchTestProviderAdapter(object):
1878
2103
    """A tool to generate a suite testing multiple branch formats at once.
1883
2108
    easy to identify.
1884
2109
    """
1885
2110
 
1886
 
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2111
    def __init__(self, transport_server, transport_readonly_server, formats,
 
2112
        vfs_transport_factory=None):
1887
2113
        self._transport_server = transport_server
1888
2114
        self._transport_readonly_server = transport_readonly_server
1889
2115
        self._formats = formats
1897
2123
            new_test.bzrdir_format = bzrdir_format
1898
2124
            new_test.branch_format = branch_format
1899
2125
            def make_new_test_id():
1900
 
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
2126
                # the format can be either a class or an instance
 
2127
                name = getattr(branch_format, '__name__',
 
2128
                        branch_format.__class__.__name__)
 
2129
                new_id = "%s(%s)" % (new_test.id(), name)
1901
2130
                return lambda: new_id
1902
2131
            new_test.id = make_new_test_id()
1903
2132
            result.addTest(new_test)
1904
2133
        return result
1905
2134
 
1906
2135
 
 
2136
######################################################################
 
2137
# results of operations
 
2138
 
 
2139
 
 
2140
class _Result(object):
 
2141
 
 
2142
    def _show_tag_conficts(self, to_file):
 
2143
        if not getattr(self, 'tag_conflicts', None):
 
2144
            return
 
2145
        to_file.write('Conflicting tags:\n')
 
2146
        for name, value1, value2 in self.tag_conflicts:
 
2147
            to_file.write('    %s\n' % (name, ))
 
2148
 
 
2149
 
 
2150
class PullResult(_Result):
 
2151
    """Result of a Branch.pull operation.
 
2152
 
 
2153
    :ivar old_revno: Revision number before pull.
 
2154
    :ivar new_revno: Revision number after pull.
 
2155
    :ivar old_revid: Tip revision id before pull.
 
2156
    :ivar new_revid: Tip revision id after pull.
 
2157
    :ivar source_branch: Source (local) branch object.
 
2158
    :ivar master_branch: Master branch of the target, or None.
 
2159
    :ivar target_branch: Target/destination branch object.
 
2160
    """
 
2161
 
 
2162
    def __int__(self):
 
2163
        # DEPRECATED: pull used to return the change in revno
 
2164
        return self.new_revno - self.old_revno
 
2165
 
 
2166
    def report(self, to_file):
 
2167
        if self.old_revid == self.new_revid:
 
2168
            to_file.write('No revisions to pull.\n')
 
2169
        else:
 
2170
            to_file.write('Now on revision %d.\n' % self.new_revno)
 
2171
        self._show_tag_conficts(to_file)
 
2172
 
 
2173
 
 
2174
class PushResult(_Result):
 
2175
    """Result of a Branch.push operation.
 
2176
 
 
2177
    :ivar old_revno: Revision number before push.
 
2178
    :ivar new_revno: Revision number after push.
 
2179
    :ivar old_revid: Tip revision id before push.
 
2180
    :ivar new_revid: Tip revision id after push.
 
2181
    :ivar source_branch: Source branch object.
 
2182
    :ivar master_branch: Master branch of the target, or None.
 
2183
    :ivar target_branch: Target/destination branch object.
 
2184
    """
 
2185
 
 
2186
    def __int__(self):
 
2187
        # DEPRECATED: push used to return the change in revno
 
2188
        return self.new_revno - self.old_revno
 
2189
 
 
2190
    def report(self, to_file):
 
2191
        """Write a human-readable description of the result."""
 
2192
        if self.old_revid == self.new_revid:
 
2193
            to_file.write('No new revisions to push.\n')
 
2194
        else:
 
2195
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
 
2196
        self._show_tag_conficts(to_file)
 
2197
 
 
2198
 
1907
2199
class BranchCheckResult(object):
1908
2200
    """Results of checking branch consistency.
1909
2201
 
1924
2216
             self.branch._format)
1925
2217
 
1926
2218
 
1927
 
######################################################################
1928
 
# predicates
1929
 
 
1930
 
 
1931
 
@deprecated_function(zero_eight)
1932
 
def is_control_file(*args, **kwargs):
1933
 
    """See bzrlib.workingtree.is_control_file."""
1934
 
    from bzrlib import workingtree
1935
 
    return workingtree.is_control_file(*args, **kwargs)
1936
 
 
1937
 
 
1938
2219
class Converter5to6(object):
1939
2220
    """Perform an in-place upgrade of format 5 to format 6"""
1940
2221
 
1949
2230
        new_branch.set_bound_location(branch.get_bound_location())
1950
2231
        new_branch.set_push_location(branch.get_push_location())
1951
2232
 
 
2233
        # New branch has no tags by default
 
2234
        new_branch.tags._set_tag_dict({})
 
2235
 
1952
2236
        # Copying done; now update target format
1953
2237
        new_branch.control_files.put_utf8('format',
1954
2238
            format.get_format_string())