~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Vincent Ladeuil
  • Date: 2007-03-13 17:00:20 UTC
  • mfrom: (2352 +trunk)
  • mto: (2323.7.1 redirection)
  • mto: This revision was merged to the branch mainline in revision 2390.
  • Revision ID: v.ladeuil+lp@free.fr-20070313170020-d0oimozr96pwconh
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
        )
41
41
from bzrlib.config import BranchConfig, TreeConfig
42
42
from bzrlib.lockable_files import LockableFiles, TransportLock
 
43
from bzrlib.tag import (
 
44
    BasicTags,
 
45
    DisabledTags,
 
46
    )
43
47
""")
44
48
 
45
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
61
65
 
62
66
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
63
67
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
64
 
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
68
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
65
69
 
66
70
 
67
71
# TODO: Maybe include checks for common corruption of newlines, etc?
88
92
    # - RBC 20060112
89
93
    base = None
90
94
 
 
95
    # override this to set the strategy for storing tags
 
96
    def _make_tags(self):
 
97
        return DisabledTags(self)
 
98
 
91
99
    def __init__(self, *ignored, **ignored_too):
92
 
        raise NotImplementedError('The Branch class is abstract')
 
100
        self.tags = self._make_tags()
93
101
 
94
102
    def break_lock(self):
95
103
        """Break a lock if one is present from another instance.
399
407
        """Mirror source into this branch.
400
408
 
401
409
        This branch is considered to be 'local', having low latency.
 
410
 
 
411
        :returns: PullResult instance
402
412
        """
403
413
        raise NotImplementedError(self.pull)
404
414
 
445
455
        """
446
456
        raise NotImplementedError(self.get_parent)
447
457
 
 
458
    def _set_config_location(self, name, url, config=None,
 
459
                             make_relative=False):
 
460
        if config is None:
 
461
            config = self.get_config()
 
462
        if url is None:
 
463
            url = ''
 
464
        elif make_relative:
 
465
            url = urlutils.relative_url(self.base, url)
 
466
        config.set_user_option(name, url)
 
467
 
 
468
    def _get_config_location(self, name, config=None):
 
469
        if config is None:
 
470
            config = self.get_config()
 
471
        location = config.get_user_option(name)
 
472
        if location == '':
 
473
            location = None
 
474
        return location
 
475
 
448
476
    def get_submit_branch(self):
449
477
        """Return the submit location of the branch.
450
478
 
463
491
        """
464
492
        self.get_config().set_user_option('submit_branch', location)
465
493
 
 
494
    def get_public_branch(self):
 
495
        """Return the public location of the branch.
 
496
 
 
497
        This is is used by merge directives.
 
498
        """
 
499
        return self._get_config_location('public_branch')
 
500
 
 
501
    def set_public_branch(self, location):
 
502
        """Return the submit location of the branch.
 
503
 
 
504
        This is the default location for bundle.  The usual
 
505
        pattern is that the user can override it by specifying a
 
506
        location.
 
507
        """
 
508
        self._set_config_location('public_branch', location)
 
509
 
466
510
    def get_push_location(self):
467
511
        """Return the None or the location to push this branch to."""
468
512
        raise NotImplementedError(self.get_push_location)
604
648
        else:
605
649
            if parent:
606
650
                destination.set_parent(parent)
 
651
        self.tags.merge_to(destination.tags)
607
652
 
608
653
    @needs_read_lock
609
654
    def check(self):
644
689
            format = bzrdir.BzrDirMetaFormat1()
645
690
            format.repository_format = weaverepo.RepositoryFormat7()
646
691
        else:
647
 
            format = self.repository.bzrdir.cloning_metadir()
 
692
            format = self.repository.bzrdir.checkout_metadir()
648
693
            format.branch_format = self._format
649
694
        return format
650
695
 
664
709
        except errors.FileExists:
665
710
            pass
666
711
        if lightweight:
667
 
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
712
            format = self._get_checkout_format()
 
713
            checkout = format.initialize_on_transport(t)
668
714
            BranchReferenceFormat().initialize(checkout, self)
669
715
        else:
670
716
            format = self._get_checkout_format()
675
721
            # pull up to the specified revision_id to set the initial 
676
722
            # branch tip correctly, and seed it with history.
677
723
            checkout_branch.pull(self, stop_revision=revision_id)
678
 
        return checkout.create_workingtree(revision_id)
 
724
        tree = checkout.create_workingtree(revision_id)
 
725
        basis_tree = tree.basis_tree()
 
726
        basis_tree.lock_read()
 
727
        try:
 
728
            for path, file_id in basis_tree.iter_references():
 
729
                reference_parent = self.reference_parent(file_id, path)
 
730
                reference_parent.create_checkout(tree.abspath(path),
 
731
                    basis_tree.get_reference_revision(file_id, path),
 
732
                    lightweight)
 
733
        finally:
 
734
            basis_tree.unlock()
 
735
        return tree
 
736
 
 
737
    def reference_parent(self, file_id, path):
 
738
        """Return the parent branch for a tree-reference file_id
 
739
        :param file_id: The file_id of the tree reference
 
740
        :param path: The path of the file_id in the tree
 
741
        :return: A branch associated with the file_id
 
742
        """
 
743
        # FIXME should provide multiple branches, based on config
 
744
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
 
745
 
 
746
    def supports_tags(self):
 
747
        return self._format.supports_tags()
679
748
 
680
749
 
681
750
class BranchFormat(object):
796
865
    def __str__(self):
797
866
        return self.get_format_string().rstrip()
798
867
 
 
868
    def supports_tags(self):
 
869
        """True if this format supports tags stored in the branch"""
 
870
        return False  # by default
 
871
 
 
872
    # XXX: Probably doesn't really belong here -- mbp 20070212
 
873
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
874
            lock_class):
 
875
        branch_transport = a_bzrdir.get_branch_transport(self)
 
876
        control_files = lockable_files.LockableFiles(branch_transport,
 
877
            lock_filename, lock_class)
 
878
        control_files.create_lock()
 
879
        control_files.lock_write()
 
880
        try:
 
881
            for filename, content in utf8_files:
 
882
                control_files.put_utf8(filename, content)
 
883
        finally:
 
884
            control_files.unlock()
 
885
 
799
886
 
800
887
class BranchHooks(dict):
801
888
    """A dictionary mapping hook name to a list of callables for branch hooks.
819
906
        self['set_rh'] = []
820
907
        # invoked after a push operation completes.
821
908
        # the api signature is
 
909
        # (push_result)
 
910
        # containing the members
822
911
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
823
912
        # where local is the local branch or None, master is the target 
824
913
        # master branch, and the rest should be self explanatory. The source
827
916
        self['post_push'] = []
828
917
        # invoked after a pull operation completes.
829
918
        # the api signature is
 
919
        # (pull_result)
 
920
        # containing the members
830
921
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
831
922
        # where local is the local branch or None, master is the target 
832
923
        # master branch, and the rest should be self explanatory. The source
955
1046
                          a_bzrdir=a_bzrdir,
956
1047
                          _repository=a_bzrdir.find_repository())
957
1048
 
958
 
    def __str__(self):
959
 
        return "Bazaar-NG Metadir branch format 5"
960
 
 
961
1049
 
962
1050
class BzrBranchFormat6(BzrBranchFormat5):
963
1051
    """Branch format with last-revision
971
1059
 
972
1060
    def get_format_string(self):
973
1061
        """See BranchFormat.get_format_string()."""
974
 
        return "Bazaar-NG branch format 6\n"
 
1062
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
975
1063
 
976
1064
    def get_format_description(self):
977
1065
        """See BranchFormat.get_format_description()."""
981
1069
        """Create a branch of this format in a_bzrdir."""
982
1070
        utf8_files = [('last-revision', '0 null:\n'),
983
1071
                      ('branch-name', ''),
984
 
                      ('branch.conf', '')
 
1072
                      ('branch.conf', ''),
 
1073
                      ('tags', ''),
985
1074
                      ]
986
1075
        return self._initialize_helper(a_bzrdir, utf8_files)
987
1076
 
1002
1091
                          a_bzrdir=a_bzrdir,
1003
1092
                          _repository=a_bzrdir.find_repository())
1004
1093
 
 
1094
    def supports_tags(self):
 
1095
        return True
 
1096
 
1005
1097
 
1006
1098
class BranchReferenceFormat(BranchFormat):
1007
1099
    """Bzr branch reference format.
1107
1199
            upgrade/recovery type use; it's not guaranteed that
1108
1200
            all operations will work on old format branches.
1109
1201
        """
 
1202
        Branch.__init__(self)
1110
1203
        if a_bzrdir is None:
1111
1204
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
1112
1205
        else:
1113
1206
            self.bzrdir = a_bzrdir
1114
 
        self._transport = self.bzrdir.transport.clone('..')
1115
 
        self._base = self._transport.base
 
1207
        # self._transport used to point to the directory containing the
 
1208
        # control directory, but was not used - now it's just the transport
 
1209
        # for the branch control files.  mbp 20070212
 
1210
        self._base = self.bzrdir.transport.clone('..').base
1116
1211
        self._format = _format
1117
1212
        if _control_files is None:
1118
1213
            raise ValueError('BzrBranch _control_files is None')
1119
1214
        self.control_files = _control_files
 
1215
        self._transport = _control_files._transport
1120
1216
        if deprecated_passed(init):
1121
1217
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1122
1218
                 "deprecated as of bzr 0.8. Please use Branch.create().",
1151
1247
    __repr__ = __str__
1152
1248
 
1153
1249
    def _get_base(self):
 
1250
        """Returns the directory containing the control directory."""
1154
1251
        return self._base
1155
1252
 
1156
1253
    base = property(_get_base, doc="The URL for the root of this branch.")
1292
1389
        self.set_revision_history(history)
1293
1390
 
1294
1391
    def _gen_revision_history(self):
1295
 
        get_cached_utf8 = cache_utf8.get_cached_utf8
1296
 
        history = [get_cached_utf8(l.rstrip('\r\n')) for l in
1297
 
                self.control_files.get('revision-history').readlines()]
 
1392
        history = self.control_files.get('revision-history').read().split('\n')
 
1393
        if history[-1:] == ['']:
 
1394
            # There shouldn't be a trailing newline, but just in case.
 
1395
            history.pop()
1298
1396
        return history
1299
1397
 
1300
1398
    @needs_read_lock
1398
1496
        :param _run_hooks: Private parameter - allow disabling of
1399
1497
            hooks, used when pushing to a master branch.
1400
1498
        """
 
1499
        result = PullResult()
 
1500
        result.source_branch = source
 
1501
        result.target_branch = self
1401
1502
        source.lock_read()
1402
1503
        try:
1403
 
            old_count, old_tip = self.last_revision_info()
 
1504
            result.old_revno, result.old_revid = self.last_revision_info()
1404
1505
            try:
1405
1506
                self.update_revisions(source, stop_revision)
1406
1507
            except DivergedBranches:
1407
1508
                if not overwrite:
1408
1509
                    raise
1409
1510
            if overwrite:
1410
 
                self.set_revision_history(source.revision_history())
1411
 
            new_count, new_tip = self.last_revision_info()
 
1511
                if stop_revision is None:
 
1512
                    stop_revision = source.last_revision()
 
1513
                self.generate_revision_history(stop_revision)
 
1514
            result.tag_conflicts = source.tags.merge_to(self.tags)
 
1515
            result.new_revno, result.new_revid = self.last_revision_info()
 
1516
            if _hook_master:
 
1517
                result.master_branch = _hook_master
 
1518
                result.local_branch = self
 
1519
            else:
 
1520
                result.master_branch = self
 
1521
                result.local_branch = None
1412
1522
            if _run_hooks:
1413
 
                if _hook_master:
1414
 
                    _hook_local = self
1415
 
                else:
1416
 
                    _hook_master = self
1417
 
                    _hook_local = None
1418
1523
                for hook in Branch.hooks['post_pull']:
1419
 
                    hook(source, _hook_local, _hook_master, old_count, old_tip,
1420
 
                        new_count, new_tip)
1421
 
            return new_count - old_count
 
1524
                    hook(result)
1422
1525
        finally:
1423
1526
            source.unlock()
 
1527
        return result
1424
1528
 
1425
1529
    def _get_parent_location(self):
1426
1530
        _locs = ['parent', 'pull', 'x-pull']
1441
1545
        :param _run_hooks: Private parameter - allow disabling of
1442
1546
            hooks, used when pushing to a master branch.
1443
1547
        """
 
1548
        result = PushResult()
 
1549
        result.source_branch = self
 
1550
        result.target_branch = target
1444
1551
        target.lock_write()
1445
1552
        try:
1446
 
            old_count, old_tip = target.last_revision_info()
 
1553
            result.old_revno, result.old_revid = target.last_revision_info()
1447
1554
            try:
1448
1555
                target.update_revisions(self, stop_revision)
1449
1556
            except DivergedBranches:
1451
1558
                    raise
1452
1559
            if overwrite:
1453
1560
                target.set_revision_history(self.revision_history())
1454
 
            new_count, new_tip = target.last_revision_info()
 
1561
            result.tag_conflicts = self.tags.merge_to(target.tags)
 
1562
            result.new_revno, result.new_revid = target.last_revision_info()
 
1563
            if _hook_master:
 
1564
                result.master_branch = _hook_master
 
1565
                result.local_branch = target
 
1566
            else:
 
1567
                result.master_branch = target
 
1568
                result.local_branch = None
1455
1569
            if _run_hooks:
1456
 
                if _hook_master:
1457
 
                    _hook_local = target
1458
 
                else:
1459
 
                    _hook_master = target
1460
 
                    _hook_local = None
1461
1570
                for hook in Branch.hooks['post_push']:
1462
 
                    hook(self, _hook_local, _hook_master, old_count, old_tip,
1463
 
                        new_count, new_tip)
1464
 
            return new_count - old_count
 
1571
                    hook(result)
1465
1572
        finally:
1466
1573
            target.unlock()
 
1574
        return result
1467
1575
 
1468
1576
    def get_parent(self):
1469
1577
        """See Branch.get_parent."""
1695
1803
        return None
1696
1804
 
1697
1805
 
 
1806
class BzrBranchExperimental(BzrBranch5):
 
1807
    """Bzr experimental branch format
 
1808
 
 
1809
    This format has:
 
1810
     - a revision-history file.
 
1811
     - a format string
 
1812
     - a lock dir guarding the branch itself
 
1813
     - all of this stored in a branch/ subdirectory
 
1814
     - works with shared repositories.
 
1815
     - a tag dictionary in the branch
 
1816
 
 
1817
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
1818
    only for testing.
 
1819
 
 
1820
    This class acts as it's own BranchFormat.
 
1821
    """
 
1822
 
 
1823
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1824
 
 
1825
    @classmethod
 
1826
    def get_format_string(cls):
 
1827
        """See BranchFormat.get_format_string()."""
 
1828
        return "Bazaar-NG branch format experimental\n"
 
1829
 
 
1830
    @classmethod
 
1831
    def get_format_description(cls):
 
1832
        """See BranchFormat.get_format_description()."""
 
1833
        return "Experimental branch format"
 
1834
 
 
1835
    @classmethod
 
1836
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
 
1837
            lock_class):
 
1838
        branch_transport = a_bzrdir.get_branch_transport(cls)
 
1839
        control_files = lockable_files.LockableFiles(branch_transport,
 
1840
            lock_filename, lock_class)
 
1841
        control_files.create_lock()
 
1842
        control_files.lock_write()
 
1843
        try:
 
1844
            for filename, content in utf8_files:
 
1845
                control_files.put_utf8(filename, content)
 
1846
        finally:
 
1847
            control_files.unlock()
 
1848
        
 
1849
    @classmethod
 
1850
    def initialize(cls, a_bzrdir):
 
1851
        """Create a branch of this format in a_bzrdir."""
 
1852
        utf8_files = [('format', cls.get_format_string()),
 
1853
                      ('revision-history', ''),
 
1854
                      ('branch-name', ''),
 
1855
                      ('tags', ''),
 
1856
                      ]
 
1857
        cls._initialize_control_files(a_bzrdir, utf8_files,
 
1858
            'lock', lockdir.LockDir)
 
1859
        return cls.open(a_bzrdir, _found=True)
 
1860
 
 
1861
    @classmethod
 
1862
    def open(cls, a_bzrdir, _found=False):
 
1863
        """Return the branch object for a_bzrdir
 
1864
 
 
1865
        _found is a private parameter, do not use it. It is used to indicate
 
1866
               if format probing has already be done.
 
1867
        """
 
1868
        if not _found:
 
1869
            format = BranchFormat.find_format(a_bzrdir)
 
1870
            assert format.__class__ == cls
 
1871
        transport = a_bzrdir.get_branch_transport(None)
 
1872
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1873
                                                     lockdir.LockDir)
 
1874
        return cls(_format=cls,
 
1875
            _control_files=control_files,
 
1876
            a_bzrdir=a_bzrdir,
 
1877
            _repository=a_bzrdir.find_repository())
 
1878
 
 
1879
    @classmethod
 
1880
    def is_supported(cls):
 
1881
        return True
 
1882
 
 
1883
    def _make_tags(self):
 
1884
        return BasicTags(self)
 
1885
 
 
1886
    @classmethod
 
1887
    def supports_tags(cls):
 
1888
        return True
 
1889
 
 
1890
 
 
1891
BranchFormat.register_format(BzrBranchExperimental)
 
1892
 
 
1893
 
1698
1894
class BzrBranch6(BzrBranch5):
1699
1895
 
1700
1896
    @needs_read_lock
1787
1983
        self.set_last_revision_info(prev_revno + len(revision_ids),
1788
1984
                                    revision_ids[-1])
1789
1985
 
1790
 
    def _set_config_location(self, name, url, config=None,
1791
 
                             make_relative=False):
1792
 
        if config is None:
1793
 
            config = self.get_config()
1794
 
        if url is None:
1795
 
            url = ''
1796
 
        elif make_relative:
1797
 
            url = urlutils.relative_url(self.base, url)
1798
 
        config.set_user_option(name, url)
1799
 
 
1800
 
 
1801
 
    def _get_config_location(self, name, config=None):
1802
 
        if config is None:
1803
 
            config = self.get_config()
1804
 
        location = config.get_user_option(name)
1805
 
        if location == '':
1806
 
            location = None
1807
 
        return location
1808
 
 
1809
1986
    @needs_write_lock
1810
1987
    def _set_parent_location(self, url):
1811
1988
        """Set the parent branch"""
1883
2060
            revno = self.revision_id_to_revno(revision_id)
1884
2061
        destination.set_last_revision_info(revno, revision_id)
1885
2062
 
 
2063
    def _make_tags(self):
 
2064
        return BasicTags(self)
 
2065
 
1886
2066
 
1887
2067
class BranchTestProviderAdapter(object):
1888
2068
    """A tool to generate a suite testing multiple branch formats at once.
1907
2087
            new_test.bzrdir_format = bzrdir_format
1908
2088
            new_test.branch_format = branch_format
1909
2089
            def make_new_test_id():
1910
 
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
2090
                # the format can be either a class or an instance
 
2091
                name = getattr(branch_format, '__name__',
 
2092
                        branch_format.__class__.__name__)
 
2093
                new_id = "%s(%s)" % (new_test.id(), name)
1911
2094
                return lambda: new_id
1912
2095
            new_test.id = make_new_test_id()
1913
2096
            result.addTest(new_test)
1914
2097
        return result
1915
2098
 
1916
2099
 
 
2100
######################################################################
 
2101
# results of operations
 
2102
 
 
2103
 
 
2104
class _Result(object):
 
2105
 
 
2106
    def _show_tag_conficts(self, to_file):
 
2107
        if not getattr(self, 'tag_conflicts', None):
 
2108
            return
 
2109
        to_file.write('Conflicting tags:\n')
 
2110
        for name, value1, value2 in self.tag_conflicts:
 
2111
            to_file.write('    %s\n' % (name, ))
 
2112
 
 
2113
 
 
2114
class PullResult(_Result):
 
2115
    """Result of a Branch.pull operation.
 
2116
 
 
2117
    :ivar old_revno: Revision number before pull.
 
2118
    :ivar new_revno: Revision number after pull.
 
2119
    :ivar old_revid: Tip revision id before pull.
 
2120
    :ivar new_revid: Tip revision id after pull.
 
2121
    :ivar source_branch: Source (local) branch object.
 
2122
    :ivar master_branch: Master branch of the target, or None.
 
2123
    :ivar target_branch: Target/destination branch object.
 
2124
    """
 
2125
 
 
2126
    def __int__(self):
 
2127
        # DEPRECATED: pull used to return the change in revno
 
2128
        return self.new_revno - self.old_revno
 
2129
 
 
2130
    def report(self, to_file):
 
2131
        if self.old_revid == self.new_revid:
 
2132
            to_file.write('No revisions to pull.\n')
 
2133
        else:
 
2134
            to_file.write('Now on revision %d.\n' % self.new_revno)
 
2135
        self._show_tag_conficts(to_file)
 
2136
 
 
2137
 
 
2138
class PushResult(_Result):
 
2139
    """Result of a Branch.push operation.
 
2140
 
 
2141
    :ivar old_revno: Revision number before push.
 
2142
    :ivar new_revno: Revision number after push.
 
2143
    :ivar old_revid: Tip revision id before push.
 
2144
    :ivar new_revid: Tip revision id after push.
 
2145
    :ivar source_branch: Source branch object.
 
2146
    :ivar master_branch: Master branch of the target, or None.
 
2147
    :ivar target_branch: Target/destination branch object.
 
2148
    """
 
2149
 
 
2150
    def __int__(self):
 
2151
        # DEPRECATED: push used to return the change in revno
 
2152
        return self.new_revno - self.old_revno
 
2153
 
 
2154
    def report(self, to_file):
 
2155
        """Write a human-readable description of the result."""
 
2156
        if self.old_revid == self.new_revid:
 
2157
            to_file.write('No new revisions to push.\n')
 
2158
        else:
 
2159
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
 
2160
        self._show_tag_conficts(to_file)
 
2161
 
 
2162
 
1917
2163
class BranchCheckResult(object):
1918
2164
    """Results of checking branch consistency.
1919
2165
 
1934
2180
             self.branch._format)
1935
2181
 
1936
2182
 
1937
 
######################################################################
1938
 
# predicates
1939
 
 
1940
 
 
1941
 
@deprecated_function(zero_eight)
1942
 
def is_control_file(*args, **kwargs):
1943
 
    """See bzrlib.workingtree.is_control_file."""
1944
 
    from bzrlib import workingtree
1945
 
    return workingtree.is_control_file(*args, **kwargs)
1946
 
 
1947
 
 
1948
2183
class Converter5to6(object):
1949
2184
    """Perform an in-place upgrade of format 5 to format 6"""
1950
2185
 
1959
2194
        new_branch.set_bound_location(branch.get_bound_location())
1960
2195
        new_branch.set_push_location(branch.get_push_location())
1961
2196
 
 
2197
        # New branch has no tags by default
 
2198
        new_branch.tags._set_tag_dict({})
 
2199
 
1962
2200
        # Copying done; now update target format
1963
2201
        new_branch.control_files.put_utf8('format',
1964
2202
            format.get_format_string())