1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
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
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,
44
from bzrlib.symbol_versioning import (
47
48
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
317
319
The delta is relative to its mainline predecessor, or the
318
320
empty tree for revision 1.
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])
326
@deprecated_method(zero_sixteen)
327
def get_root_id(self):
328
"""Return the id of this branches root
330
Deprecated: branches don't have root ids-- trees do.
331
Use basis_tree().get_root_id() instead.
333
raise NotImplementedError(self.get_root_id)
335
327
def print_file(self, file, revision_id):
336
328
"""Print `file` to stdout."""
337
329
raise NotImplementedError(self.print_file)
451
443
if stop_revision is None:
452
444
stop_revision = other_len
454
assert isinstance(stop_revision, int)
455
446
if stop_revision > other_len:
456
447
raise errors.NoSuchRevision(self, stop_revision)
457
448
return other_history[self_len:stop_revision]
704
695
:return: A BranchCheckResult.
706
697
mainline_parent_id = None
707
for revision_id in self.revision_history():
698
last_revno, last_revision_id = self.last_revision_info()
699
real_rev_history = list(self.repository.iter_reverse_revision_history(
701
real_rev_history.reverse()
702
if len(real_rev_history) != last_revno:
703
raise errors.BzrCheckError('revno does not match len(mainline)'
704
' %s != %s' % (last_revno, len(real_rev_history)))
705
# TODO: We should probably also check that real_rev_history actually
706
# matches self.revision_history()
707
for revision_id in real_rev_history:
709
709
revision = self.repository.get_revision(revision_id)
710
710
except errors.NoSuchRevision, e:
783
783
basis_tree.unlock()
787
def reconcile(self, thorough=True):
788
"""Make sure the data stored in this branch is consistent."""
789
from bzrlib.reconcile import BranchReconciler
790
reconciler = BranchReconciler(self, thorough=thorough)
791
reconciler.reconcile()
786
794
def reference_parent(self, file_id, path):
787
795
"""Return the parent branch for a tree-reference file_id
788
796
:param file_id: The file_id of the tree reference
900
908
control_files.create_lock()
901
909
control_files.lock_write()
903
control_files.put_utf8('format', self.get_format_string())
911
utf8_files += [('format', self.get_format_string())]
905
for file, content in utf8_files:
906
control_files.put_utf8(file, content)
913
for (filename, content) in utf8_files:
914
branch_transport.put_bytes(
916
mode=control_files._file_mode)
908
918
control_files.unlock()
909
919
return self.open(a_bzrdir, _found=True)
949
958
"""True if this format supports tags stored in the branch"""
950
959
return False # by default
952
# XXX: Probably doesn't really belong here -- mbp 20070212
953
def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
955
branch_transport = a_bzrdir.get_branch_transport(self)
956
control_files = lockable_files.LockableFiles(branch_transport,
957
lock_filename, lock_class)
958
control_files.create_lock()
959
control_files.lock_write()
961
for filename, content in utf8_files:
962
control_files.put_utf8(filename, content)
964
control_files.unlock()
967
962
class BranchHooks(Hooks):
968
963
"""A dictionary mapping hook name to a list of callables for branch hooks.
1025
1020
# local is the local branch or None, master is the target branch,
1026
1021
# and an empty branch recieves new_revno of 0, new_revid of None.
1027
1022
self['post_uncommit'] = []
1024
# Invoked after the tip of a branch changes.
1025
# the api signature is
1026
# (params) where params is a ChangeBranchTipParams with the members
1027
# (branch, old_revno, new_revno, old_revid, new_revid)
1028
self['post_change_branch_tip'] = []
1030
1031
# install the default hooks into the Branch class.
1031
1032
Branch.hooks = BranchHooks()
1035
class ChangeBranchTipParams(object):
1036
"""Object holding parameters passed to *_change_branch_tip hooks.
1038
There are 5 fields that hooks may wish to access:
1040
:ivar branch: the branch being changed
1041
:ivar old_revno: revision number before the change
1042
:ivar new_revno: revision number after the change
1043
:ivar old_revid: revision id before the change
1044
:ivar new_revid: revision id after the change
1046
The revid fields are strings. The revno fields are integers.
1049
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1050
"""Create a group of ChangeBranchTip parameters.
1052
:param branch: The branch being changed.
1053
:param old_revno: Revision number before the change.
1054
:param new_revno: Revision number after the change.
1055
:param old_revid: Tip revision id before the change.
1056
:param new_revid: Tip revision id after the change.
1058
self.branch = branch
1059
self.old_revno = old_revno
1060
self.new_revno = new_revno
1061
self.old_revid = old_revid
1062
self.new_revid = new_revid
1034
1065
class BzrBranchFormat4(BranchFormat):
1035
1066
"""Bzr branch format 4.
1115
1146
format = BranchFormat.find_format(a_bzrdir)
1116
assert format.__class__ == self.__class__
1147
if format.__class__ != self.__class__:
1148
raise AssertionError("wrong format %r found for %r" %
1118
1151
transport = a_bzrdir.get_branch_transport(None)
1119
1152
control_files = lockable_files.LockableFiles(transport, 'lock',
1163
1196
format = BranchFormat.find_format(a_bzrdir)
1164
assert format.__class__ == self.__class__
1197
if format.__class__ != self.__class__:
1198
raise AssertionError("wrong format %r found for %r" %
1165
1200
transport = a_bzrdir.get_branch_transport(None)
1166
1201
control_files = lockable_files.LockableFiles(transport, 'lock',
1167
1202
lockdir.LockDir)
1243
1278
format = BranchFormat.find_format(a_bzrdir)
1244
assert format.__class__ == self.__class__
1279
if format.__class__ != self.__class__:
1280
raise AssertionError("wrong format %r found for %r" %
1245
1282
if location is None:
1246
1283
location = self.get_reference(a_bzrdir)
1247
1284
real_bzrdir = bzrdir.BzrDir.open(
1276
1313
Note that it's "local" in the context of the filesystem; it doesn't
1277
1314
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1278
1315
it's writable, and can be accessed via the normal filesystem API.
1317
:ivar _transport: Transport for file operations on this branch's
1318
control files, typically pointing to the .bzr/branch directory.
1319
:ivar repository: Repository for this branch.
1320
:ivar base: The url of the base directory for this branch; the one
1321
containing the .bzr directory.
1281
1324
def __init__(self, _format=None,
1286
1329
raise ValueError('a_bzrdir must be supplied')
1288
1331
self.bzrdir = a_bzrdir
1289
# self._transport used to point to the directory containing the
1290
# control directory, but was not used - now it's just the transport
1291
# for the branch control files. mbp 20070212
1292
1332
self._base = self.bzrdir.transport.clone('..').base
1333
# XXX: We should be able to just do
1334
# self.base = self.bzrdir.root_transport.base
1335
# but this does not quite work yet -- mbp 20080522
1293
1336
self._format = _format
1294
1337
if _control_files is None:
1295
1338
raise ValueError('BzrBranch _control_files is None')
1309
1352
base = property(_get_base, doc="The URL for the root of this branch.")
1354
@deprecated_method(deprecated_in((0, 16, 0)))
1311
1355
def abspath(self, name):
1312
1356
"""See Branch.abspath."""
1313
return self.control_files._transport.abspath(name)
1316
@deprecated_method(zero_sixteen)
1318
def get_root_id(self):
1319
"""See Branch.get_root_id."""
1320
tree = self.repository.revision_tree(self.last_revision())
1321
return tree.get_root_id()
1357
return self._transport.abspath(name)
1323
1359
def is_locked(self):
1324
1360
return self.control_files.is_locked()
1370
1406
This performs the actual writing to disk.
1371
1407
It is intended to be called by BzrBranch5.set_revision_history."""
1372
self.control_files.put_bytes(
1373
'revision-history', '\n'.join(history))
1408
self._transport.put_bytes(
1409
'revision-history', '\n'.join(history),
1410
mode=self.control_files._file_mode)
1375
1412
@needs_write_lock
1376
1413
def set_revision_history(self, rev_history):
1383
1420
for hook in Branch.hooks['set_rh']:
1384
1421
hook(self, rev_history)
1423
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1424
"""Run the post_change_branch_tip hooks."""
1425
hooks = Branch.hooks['post_change_branch_tip']
1428
new_revno, new_revid = self.last_revision_info()
1429
params = ChangeBranchTipParams(
1430
self, old_revno, new_revno, old_revid, new_revid)
1386
1434
@needs_write_lock
1387
1435
def set_last_revision_info(self, revno, revision_id):
1388
1436
"""Set the last revision of this branch.
1398
1446
revision_id = _mod_revision.ensure_null(revision_id)
1447
old_revno, old_revid = self.last_revision_info()
1448
# this old format stores the full history, but this api doesn't
1449
# provide it, so we must generate, and might as well check it's
1399
1451
history = self._lefthand_history(revision_id)
1400
assert len(history) == revno, '%d != %d' % (len(history), revno)
1452
if len(history) != revno:
1453
raise AssertionError('%d != %d' % (len(history), revno))
1401
1454
self.set_revision_history(history)
1455
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1403
1457
def _gen_revision_history(self):
1404
history = self.control_files.get('revision-history').read().split('\n')
1458
history = self._transport.get_bytes('revision-history').split('\n')
1405
1459
if history[-1:] == ['']:
1406
1460
# There shouldn't be a trailing newline, but just in case.
1476
1530
elif heads == set([stop_revision, last_rev]):
1477
1531
# These branches have diverged
1478
1532
raise errors.DivergedBranches(self, other)
1479
assert heads == set([stop_revision])
1533
elif heads != set([stop_revision]):
1534
raise AssertionError("invalid heads: %r" % heads)
1480
1535
if other_last_revision == stop_revision:
1481
1536
self.set_last_revision_info(other_last_revno,
1482
1537
other_last_revision)
1532
1587
_locs = ['parent', 'pull', 'x-pull']
1533
1588
for l in _locs:
1535
return self.control_files.get(l).read().strip('\n')
1590
return self._transport.get_bytes(l).strip('\n')
1536
1591
except errors.NoSuchFile:
1615
1670
result.source_branch = self
1616
1671
result.target_branch = target
1617
1672
result.old_revno, result.old_revid = target.last_revision_info()
1619
target.update_revisions(self, stop_revision)
1620
except errors.DivergedBranches:
1624
target.set_revision_history(self.revision_history())
1673
target.update_revisions(self, stop_revision, overwrite)
1625
1674
result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1626
1675
result.new_revno, result.new_revid = target.last_revision_info()
1629
1678
def get_parent(self):
1630
1679
"""See Branch.get_parent."""
1632
assert self.base[-1] == '/'
1633
1680
parent = self._get_parent_location()
1634
1681
if parent is None:
1654
1701
# TODO: Maybe delete old location files?
1655
1702
# URLs should never be unicode, even on the local fs,
1656
1703
# FIXUP this and get_parent in a future branch format bump:
1657
# read and rewrite the file, and have the new format code read
1658
# using .get not .get_utf8. RBC 20060125
1704
# read and rewrite the file. RBC 20060125
1659
1705
if url is not None:
1660
1706
if isinstance(url, unicode):
1662
1708
url = url.encode('ascii')
1663
1709
except UnicodeEncodeError:
1664
1710
raise errors.InvalidURL(url,
1670
1716
def _set_parent_location(self, url):
1671
1717
if url is None:
1672
self.control_files._transport.delete('parent')
1718
self._transport.delete('parent')
1674
assert isinstance(url, str)
1675
self.control_files.put_bytes('parent', url + '\n')
1720
self._transport.put_bytes('parent', url + '\n',
1721
mode=self.control_files._file_mode)
1678
1724
class BzrBranch5(BzrBranch):
1752
1798
:param location: URL to the target branch
1755
self.control_files.put_utf8('bound', location+'\n')
1801
self._transport.put_bytes('bound', location+'\n',
1802
mode=self.bzrdir._get_file_mode())
1758
self.control_files._transport.delete('bound')
1805
self._transport.delete('bound')
1759
1806
except errors.NoSuchFile:
1832
1879
return self._last_revision_info_cache
1834
1881
def _last_revision_info(self):
1835
revision_string = self.control_files.get('last-revision').read()
1882
revision_string = self._transport.get_bytes('last-revision')
1836
1883
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1837
1884
revision_id = cache_utf8.get_cached_utf8(revision_id)
1838
1885
revno = int(revno)
1847
1894
Intended to be called by set_last_revision_info and
1848
1895
_write_revision_history.
1850
assert revision_id is not None, "Use NULL_REVISION, not None"
1897
revision_id = _mod_revision.ensure_null(revision_id)
1851
1898
out_string = '%d %s\n' % (revno, revision_id)
1852
self.control_files.put_bytes('last-revision', out_string)
1899
self._transport.put_bytes('last-revision', out_string,
1900
mode=self.control_files._file_mode)
1854
1902
@needs_write_lock
1855
1903
def set_last_revision_info(self, revno, revision_id):
1856
1904
revision_id = _mod_revision.ensure_null(revision_id)
1905
old_revno, old_revid = self.last_revision_info()
1857
1906
if self._get_append_revisions_only():
1858
1907
self._check_history_violation(revision_id)
1859
1908
self._write_last_revision_info(revno, revision_id)
1860
1909
self._clear_cached_state()
1861
1910
self._last_revision_info_cache = revno, revision_id
1911
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1863
1913
def _check_history_violation(self, revision_id):
1864
1914
last_revision = _mod_revision.ensure_null(self.last_revision())
1894
1944
iterator = repo.iter_reverse_revision_history(start_revision)
1895
1945
#skip the last revision in the list
1896
1946
next_revision = iterator.next()
1897
assert next_revision == start_revision
1898
1947
for revision_id in iterator:
1899
1948
self._partial_revision_history_cache.append(revision_id)
1900
1949
if (stop_index is not None and
2030
2079
raise errors.NoSuchRevision(self, revno)
2032
2081
if history is not None:
2033
assert len(history) == last_revno, 'revno/history mismatch'
2034
2082
return history[revno - 1]
2036
2084
index = last_revno - revno
2149
2197
new_branch = format.open(branch.bzrdir, _found=True)
2151
2199
# Copy source data into target
2152
new_branch.set_last_revision_info(*branch.last_revision_info())
2200
new_branch._write_last_revision_info(*branch.last_revision_info())
2153
2201
new_branch.set_parent(branch.get_parent())
2154
2202
new_branch.set_bound_location(branch.get_bound_location())
2155
2203
new_branch.set_push_location(branch.get_push_location())
2158
2206
new_branch.tags._set_tag_dict({})
2160
2208
# Copying done; now update target format
2161
new_branch.control_files.put_utf8('format',
2162
format.get_format_string())
2209
new_branch._transport.put_bytes('format',
2210
format.get_format_string(),
2211
mode=new_branch.control_files._file_mode)
2164
2213
# Clean up old files
2165
new_branch.control_files._transport.delete('revision-history')
2214
new_branch._transport.delete('revision-history')
2167
2216
branch.set_parent(None)
2168
2217
except errors.NoSuchFile: