~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2008-06-11 02:36:40 UTC
  • mfrom: (3490 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3492.
  • Revision ID: mbp@sourcefrog.net-20080611023640-db0lqd75yueksdw7
Merge news

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
41
41
 
42
42
from bzrlib.decorators import needs_read_lock, needs_write_lock
43
43
from bzrlib.hooks import Hooks
44
 
from bzrlib.symbol_versioning import (deprecated_method,
45
 
                                      zero_sixteen,
46
 
                                      )
 
44
from bzrlib.symbol_versioning import (
 
45
    deprecated_in,
 
46
    deprecated_method,
 
47
    )
47
48
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
48
49
 
49
50
 
221
222
        """
222
223
        self.control_files.dont_leave_in_place()
223
224
 
 
225
    @deprecated_method(deprecated_in((0, 16, 0)))
224
226
    def abspath(self, name):
225
227
        """Return absolute filename for something in the branch
226
228
        
317
319
        The delta is relative to its mainline predecessor, or the
318
320
        empty tree for revision 1.
319
321
        """
320
 
        assert isinstance(revno, int)
321
322
        rh = self.revision_history()
322
323
        if not (1 <= revno <= len(rh)):
323
324
            raise errors.InvalidRevisionNumber(revno)
324
325
        return self.repository.get_revision_delta(rh[revno-1])
325
326
 
326
 
    @deprecated_method(zero_sixteen)
327
 
    def get_root_id(self):
328
 
        """Return the id of this branches root
329
 
 
330
 
        Deprecated: branches don't have root ids-- trees do.
331
 
        Use basis_tree().get_root_id() instead.
332
 
        """
333
 
        raise NotImplementedError(self.get_root_id)
334
 
 
335
327
    def print_file(self, file, revision_id):
336
328
        """Print `file` to stdout."""
337
329
        raise NotImplementedError(self.print_file)
433
425
        else:
434
426
            return (0, _mod_revision.NULL_REVISION)
435
427
 
 
428
    @deprecated_method(deprecated_in((1, 6, 0)))
436
429
    def missing_revisions(self, other, stop_revision=None):
437
430
        """Return a list of new revisions that would perfectly fit.
438
431
        
451
444
        if stop_revision is None:
452
445
            stop_revision = other_len
453
446
        else:
454
 
            assert isinstance(stop_revision, int)
455
447
            if stop_revision > other_len:
456
448
                raise errors.NoSuchRevision(self, stop_revision)
457
449
        return other_history[self_len:stop_revision]
458
450
 
459
 
    def update_revisions(self, other, stop_revision=None):
 
451
    @needs_write_lock
 
452
    def update_revisions(self, other, stop_revision=None, overwrite=False,
 
453
                         graph=None):
460
454
        """Pull in new perfect-fit revisions.
461
455
 
462
456
        :param other: Another Branch to pull from
463
457
        :param stop_revision: Updated until the given revision
 
458
        :param overwrite: Always set the branch pointer, rather than checking
 
459
            to see if it is a proper descendant.
 
460
        :param graph: A Graph object that can be used to query history
 
461
            information. This can be None.
464
462
        :return: None
465
463
        """
466
 
        raise NotImplementedError(self.update_revisions)
 
464
        other.lock_read()
 
465
        try:
 
466
            other_revno, other_last_revision = other.last_revision_info()
 
467
            stop_revno = None # unknown
 
468
            if stop_revision is None:
 
469
                stop_revision = other_last_revision
 
470
                if _mod_revision.is_null(stop_revision):
 
471
                    # if there are no commits, we're done.
 
472
                    return
 
473
                stop_revno = other_revno
 
474
 
 
475
            # what's the current last revision, before we fetch [and change it
 
476
            # possibly]
 
477
            last_rev = _mod_revision.ensure_null(self.last_revision())
 
478
            # we fetch here so that we don't process data twice in the common
 
479
            # case of having something to pull, and so that the check for 
 
480
            # already merged can operate on the just fetched graph, which will
 
481
            # be cached in memory.
 
482
            self.fetch(other, stop_revision)
 
483
            # Check to see if one is an ancestor of the other
 
484
            if not overwrite:
 
485
                if graph is None:
 
486
                    graph = self.repository.get_graph()
 
487
                heads = graph.heads([stop_revision, last_rev])
 
488
                if heads == set([last_rev]):
 
489
                    # The current revision is a decendent of the target,
 
490
                    # nothing to do
 
491
                    return
 
492
                elif heads == set([stop_revision, last_rev]):
 
493
                    # These branches have diverged
 
494
                    raise errors.DivergedBranches(self, other)
 
495
                elif heads != set([stop_revision]):
 
496
                    raise AssertionError("invalid heads: %r" % heads)
 
497
            if stop_revno is None:
 
498
                if graph is None:
 
499
                    graph = self.repository.get_graph()
 
500
                this_revno, this_last_revision = self.last_revision_info()
 
501
                stop_revno = graph.find_distance_to_null(stop_revision,
 
502
                                [(other_last_revision, other_revno),
 
503
                                 (this_last_revision, this_revno)])
 
504
            self.set_last_revision_info(stop_revno, stop_revision)
 
505
        finally:
 
506
            other.unlock()
 
507
 
 
508
 
467
509
 
468
510
    def revision_id_to_revno(self, revision_id):
469
511
        """Given a revision id, return its revno"""
917
959
        control_files.create_lock()
918
960
        control_files.lock_write()
919
961
        if set_format:
920
 
            control_files.put_utf8('format', self.get_format_string())
 
962
            utf8_files += [('format', self.get_format_string())]
921
963
        try:
922
 
            for file, content in utf8_files:
923
 
                control_files.put_utf8(file, content)
 
964
            for (filename, content) in utf8_files:
 
965
                branch_transport.put_bytes(
 
966
                    filename, content,
 
967
                    mode=a_bzrdir._get_file_mode())
924
968
        finally:
925
969
            control_files.unlock()
926
970
        return self.open(a_bzrdir, _found=True)
956
1000
 
957
1001
    @classmethod
958
1002
    def unregister_format(klass, format):
959
 
        assert klass._formats[format.get_format_string()] is format
960
1003
        del klass._formats[format.get_format_string()]
961
1004
 
962
1005
    def __str__(self):
966
1009
        """True if this format supports tags stored in the branch"""
967
1010
        return False  # by default
968
1011
 
969
 
    # XXX: Probably doesn't really belong here -- mbp 20070212
970
 
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
971
 
            lock_class):
972
 
        branch_transport = a_bzrdir.get_branch_transport(self)
973
 
        control_files = lockable_files.LockableFiles(branch_transport,
974
 
            lock_filename, lock_class)
975
 
        control_files.create_lock()
976
 
        control_files.lock_write()
977
 
        try:
978
 
            for filename, content in utf8_files:
979
 
                control_files.put_utf8(filename, content)
980
 
        finally:
981
 
            control_files.unlock()
982
 
 
983
1012
 
984
1013
class BranchHooks(Hooks):
985
1014
    """A dictionary mapping hook name to a list of callables for branch hooks.
1166
1195
        """
1167
1196
        if not _found:
1168
1197
            format = BranchFormat.find_format(a_bzrdir)
1169
 
            assert format.__class__ == self.__class__
 
1198
            if format.__class__ != self.__class__:
 
1199
                raise AssertionError("wrong format %r found for %r" %
 
1200
                    (format, self))
1170
1201
        try:
1171
1202
            transport = a_bzrdir.get_branch_transport(None)
1172
1203
            control_files = lockable_files.LockableFiles(transport, 'lock',
1214
1245
        """
1215
1246
        if not _found:
1216
1247
            format = BranchFormat.find_format(a_bzrdir)
1217
 
            assert format.__class__ == self.__class__
 
1248
            if format.__class__ != self.__class__:
 
1249
                raise AssertionError("wrong format %r found for %r" %
 
1250
                    (format, self))
1218
1251
        transport = a_bzrdir.get_branch_transport(None)
1219
1252
        control_files = lockable_files.LockableFiles(transport, 'lock',
1220
1253
                                                     lockdir.LockDir)
1294
1327
        """
1295
1328
        if not _found:
1296
1329
            format = BranchFormat.find_format(a_bzrdir)
1297
 
            assert format.__class__ == self.__class__
 
1330
            if format.__class__ != self.__class__:
 
1331
                raise AssertionError("wrong format %r found for %r" %
 
1332
                    (format, self))
1298
1333
        if location is None:
1299
1334
            location = self.get_reference(a_bzrdir)
1300
1335
        real_bzrdir = bzrdir.BzrDir.open(
1329
1364
    Note that it's "local" in the context of the filesystem; it doesn't
1330
1365
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1331
1366
    it's writable, and can be accessed via the normal filesystem API.
 
1367
 
 
1368
    :ivar _transport: Transport for file operations on this branch's 
 
1369
        control files, typically pointing to the .bzr/branch directory.
 
1370
    :ivar repository: Repository for this branch.
 
1371
    :ivar base: The url of the base directory for this branch; the one 
 
1372
        containing the .bzr directory.
1332
1373
    """
1333
1374
    
1334
1375
    def __init__(self, _format=None,
1339
1380
            raise ValueError('a_bzrdir must be supplied')
1340
1381
        else:
1341
1382
            self.bzrdir = a_bzrdir
1342
 
        # self._transport used to point to the directory containing the
1343
 
        # control directory, but was not used - now it's just the transport
1344
 
        # for the branch control files.  mbp 20070212
1345
1383
        self._base = self.bzrdir.transport.clone('..').base
 
1384
        # XXX: We should be able to just do
 
1385
        #   self.base = self.bzrdir.root_transport.base
 
1386
        # but this does not quite work yet -- mbp 20080522
1346
1387
        self._format = _format
1347
1388
        if _control_files is None:
1348
1389
            raise ValueError('BzrBranch _control_files is None')
1361
1402
 
1362
1403
    base = property(_get_base, doc="The URL for the root of this branch.")
1363
1404
 
 
1405
    @deprecated_method(deprecated_in((0, 16, 0)))
1364
1406
    def abspath(self, name):
1365
1407
        """See Branch.abspath."""
1366
 
        return self.control_files._transport.abspath(name)
1367
 
 
1368
 
 
1369
 
    @deprecated_method(zero_sixteen)
1370
 
    @needs_read_lock
1371
 
    def get_root_id(self):
1372
 
        """See Branch.get_root_id."""
1373
 
        tree = self.repository.revision_tree(self.last_revision())
1374
 
        return tree.get_root_id()
 
1408
        return self._transport.abspath(name)
1375
1409
 
1376
1410
    def is_locked(self):
1377
1411
        return self.control_files.is_locked()
1422
1456
 
1423
1457
        This performs the actual writing to disk.
1424
1458
        It is intended to be called by BzrBranch5.set_revision_history."""
1425
 
        self.control_files.put_bytes(
1426
 
            'revision-history', '\n'.join(history))
 
1459
        self._transport.put_bytes(
 
1460
            'revision-history', '\n'.join(history),
 
1461
            mode=self.bzrdir._get_file_mode())
1427
1462
 
1428
1463
    @needs_write_lock
1429
1464
    def set_revision_history(self, rev_history):
1461
1496
        """
1462
1497
        revision_id = _mod_revision.ensure_null(revision_id)
1463
1498
        old_revno, old_revid = self.last_revision_info()
 
1499
        # this old format stores the full history, but this api doesn't
 
1500
        # provide it, so we must generate, and might as well check it's
 
1501
        # correct
1464
1502
        history = self._lefthand_history(revision_id)
1465
 
        assert len(history) == revno, '%d != %d' % (len(history), revno)
 
1503
        if len(history) != revno:
 
1504
            raise AssertionError('%d != %d' % (len(history), revno))
1466
1505
        self.set_revision_history(history)
1467
1506
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1468
1507
 
1469
1508
    def _gen_revision_history(self):
1470
 
        history = self.control_files.get('revision-history').read().split('\n')
 
1509
        history = self._transport.get_bytes('revision-history').split('\n')
1471
1510
        if history[-1:] == ['']:
1472
1511
            # There shouldn't be a trailing newline, but just in case.
1473
1512
            history.pop()
1512
1551
        self.set_revision_history(self._lefthand_history(revision_id,
1513
1552
            last_rev, other_branch))
1514
1553
 
1515
 
    @needs_write_lock
1516
 
    def update_revisions(self, other, stop_revision=None, overwrite=False):
1517
 
        """See Branch.update_revisions."""
1518
 
        other.lock_read()
1519
 
        try:
1520
 
            other_last_revno, other_last_revision = other.last_revision_info()
1521
 
            if stop_revision is None:
1522
 
                stop_revision = other_last_revision
1523
 
                if _mod_revision.is_null(stop_revision):
1524
 
                    # if there are no commits, we're done.
1525
 
                    return
1526
 
            # whats the current last revision, before we fetch [and change it
1527
 
            # possibly]
1528
 
            last_rev = _mod_revision.ensure_null(self.last_revision())
1529
 
            # we fetch here so that we don't process data twice in the common
1530
 
            # case of having something to pull, and so that the check for 
1531
 
            # already merged can operate on the just fetched graph, which will
1532
 
            # be cached in memory.
1533
 
            self.fetch(other, stop_revision)
1534
 
            # Check to see if one is an ancestor of the other
1535
 
            if not overwrite:
1536
 
                heads = self.repository.get_graph().heads([stop_revision,
1537
 
                                                           last_rev])
1538
 
                if heads == set([last_rev]):
1539
 
                    # The current revision is a decendent of the target,
1540
 
                    # nothing to do
1541
 
                    return
1542
 
                elif heads == set([stop_revision, last_rev]):
1543
 
                    # These branches have diverged
1544
 
                    raise errors.DivergedBranches(self, other)
1545
 
                assert heads == set([stop_revision])
1546
 
            if other_last_revision == stop_revision:
1547
 
                self.set_last_revision_info(other_last_revno,
1548
 
                                            other_last_revision)
1549
 
            else:
1550
 
                # TODO: jam 2007-11-29 Is there a way to determine the
1551
 
                #       revno without searching all of history??
1552
 
                if overwrite:
1553
 
                    self.generate_revision_history(stop_revision)
1554
 
                else:
1555
 
                    self.generate_revision_history(stop_revision,
1556
 
                        last_rev=last_rev, other_branch=other)
1557
 
        finally:
1558
 
            other.unlock()
1559
 
 
1560
1554
    def basis_tree(self):
1561
1555
        """See Branch.basis_tree."""
1562
1556
        return self.repository.revision_tree(self.last_revision())
1577
1571
        result.target_branch = self
1578
1572
        source.lock_read()
1579
1573
        try:
 
1574
            # We assume that during 'pull' the local repository is closer than
 
1575
            # the remote one.
 
1576
            graph = self.repository.get_graph(source.repository)
1580
1577
            result.old_revno, result.old_revid = self.last_revision_info()
1581
 
            self.update_revisions(source, stop_revision, overwrite=overwrite)
 
1578
            self.update_revisions(source, stop_revision, overwrite=overwrite,
 
1579
                                  graph=graph)
1582
1580
            result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1583
1581
            result.new_revno, result.new_revid = self.last_revision_info()
1584
1582
            if _hook_master:
1598
1596
        _locs = ['parent', 'pull', 'x-pull']
1599
1597
        for l in _locs:
1600
1598
            try:
1601
 
                return self.control_files.get(l).read().strip('\n')
 
1599
                return self._transport.get_bytes(l).strip('\n')
1602
1600
            except errors.NoSuchFile:
1603
1601
                pass
1604
1602
        return None
1681
1679
        result.source_branch = self
1682
1680
        result.target_branch = target
1683
1681
        result.old_revno, result.old_revid = target.last_revision_info()
1684
 
        try:
1685
 
            target.update_revisions(self, stop_revision)
1686
 
        except errors.DivergedBranches:
1687
 
            if not overwrite:
1688
 
                raise
1689
 
        if overwrite:
1690
 
            target.set_revision_history(self.revision_history())
 
1682
 
 
1683
        # We assume that during 'push' this repository is closer than
 
1684
        # the target.
 
1685
        graph = self.repository.get_graph(target.repository)
 
1686
        target.update_revisions(self, stop_revision, overwrite=overwrite,
 
1687
                                graph=graph)
1691
1688
        result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1692
1689
        result.new_revno, result.new_revid = target.last_revision_info()
1693
1690
        return result
1694
1691
 
1695
1692
    def get_parent(self):
1696
1693
        """See Branch.get_parent."""
1697
 
 
1698
 
        assert self.base[-1] == '/'
1699
1694
        parent = self._get_parent_location()
1700
1695
        if parent is None:
1701
1696
            return parent
1723
1718
        # read and rewrite the file. RBC 20060125
1724
1719
        if url is not None:
1725
1720
            if isinstance(url, unicode):
1726
 
                try: 
 
1721
                try:
1727
1722
                    url = url.encode('ascii')
1728
1723
                except UnicodeEncodeError:
1729
1724
                    raise errors.InvalidURL(url,
1734
1729
 
1735
1730
    def _set_parent_location(self, url):
1736
1731
        if url is None:
1737
 
            self.control_files._transport.delete('parent')
 
1732
            self._transport.delete('parent')
1738
1733
        else:
1739
 
            assert isinstance(url, str)
1740
 
            self.control_files.put_bytes('parent', url + '\n')
 
1734
            self._transport.put_bytes('parent', url + '\n',
 
1735
                mode=self.bzrdir._get_file_mode())
1741
1736
 
1742
1737
 
1743
1738
class BzrBranch5(BzrBranch):
1817
1812
        :param location: URL to the target branch
1818
1813
        """
1819
1814
        if location:
1820
 
            self.control_files.put_utf8('bound', location+'\n')
 
1815
            self._transport.put_bytes('bound', location+'\n',
 
1816
                mode=self.bzrdir._get_file_mode())
1821
1817
        else:
1822
1818
            try:
1823
 
                self.control_files._transport.delete('bound')
 
1819
                self._transport.delete('bound')
1824
1820
            except errors.NoSuchFile:
1825
1821
                return False
1826
1822
            return True
1897
1893
        return self._last_revision_info_cache
1898
1894
 
1899
1895
    def _last_revision_info(self):
1900
 
        revision_string = self.control_files.get('last-revision').read()
 
1896
        revision_string = self._transport.get_bytes('last-revision')
1901
1897
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1902
1898
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1903
1899
        revno = int(revno)
1912
1908
        Intended to be called by set_last_revision_info and
1913
1909
        _write_revision_history.
1914
1910
        """
1915
 
        assert revision_id is not None, "Use NULL_REVISION, not None"
 
1911
        revision_id = _mod_revision.ensure_null(revision_id)
1916
1912
        out_string = '%d %s\n' % (revno, revision_id)
1917
 
        self.control_files.put_bytes('last-revision', out_string)
 
1913
        self._transport.put_bytes('last-revision', out_string,
 
1914
            mode=self.bzrdir._get_file_mode())
1918
1915
 
1919
1916
    @needs_write_lock
1920
1917
    def set_last_revision_info(self, revno, revision_id):
1961
1958
            iterator = repo.iter_reverse_revision_history(start_revision)
1962
1959
            #skip the last revision in the list
1963
1960
            next_revision = iterator.next()
1964
 
            assert next_revision == start_revision
1965
1961
        for revision_id in iterator:
1966
1962
            self._partial_revision_history_cache.append(revision_id)
1967
1963
            if (stop_index is not None and
2097
2093
            raise errors.NoSuchRevision(self, revno)
2098
2094
 
2099
2095
        if history is not None:
2100
 
            assert len(history) == last_revno, 'revno/history mismatch'
2101
2096
            return history[revno - 1]
2102
2097
 
2103
2098
        index = last_revno - revno
2145
2140
    :ivar old_revid: Tip revision id before pull.
2146
2141
    :ivar new_revid: Tip revision id after pull.
2147
2142
    :ivar source_branch: Source (local) branch object.
2148
 
    :ivar master_branch: Master branch of the target, or None.
 
2143
    :ivar master_branch: Master branch of the target, or the target if no
 
2144
        Master
 
2145
    :ivar local_branch: target branch if there is a Master, else None
2149
2146
    :ivar target_branch: Target/destination branch object.
 
2147
    :ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2150
2148
    """
2151
2149
 
2152
2150
    def __int__(self):
2225
2223
        new_branch.tags._set_tag_dict({})
2226
2224
 
2227
2225
        # Copying done; now update target format
2228
 
        new_branch.control_files.put_utf8('format',
2229
 
            format.get_format_string())
 
2226
        new_branch._transport.put_bytes('format',
 
2227
            format.get_format_string(),
 
2228
            mode=new_branch.bzrdir._get_file_mode())
2230
2229
 
2231
2230
        # Clean up old files
2232
 
        new_branch.control_files._transport.delete('revision-history')
 
2231
        new_branch._transport.delete('revision-history')
2233
2232
        try:
2234
2233
            branch.set_parent(None)
2235
2234
        except errors.NoSuchFile: