1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
1
# Copyright (C) 2005-2010 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
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
49
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
50
50
from bzrlib.hooks import HookPoint, Hooks
51
51
from bzrlib.inter import InterObject
52
from bzrlib.lock import _RelockDebugMixin
52
53
from bzrlib import registry
53
54
from bzrlib.symbol_versioning import (
149
150
if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
150
151
self._partial_revision_history_cache.pop()
153
def _get_check_refs(self):
154
"""Get the references needed for check().
158
revid = self.last_revision()
159
return [('revision-existence', revid), ('lefthand-distance', revid)]
153
162
def open(base, _unsupported=False, possible_transports=None):
154
163
"""Open the branch rooted at base.
438
447
# start_revision_id.
439
448
if self._merge_sorted_revisions_cache is None:
440
449
last_revision = self.last_revision()
441
graph = self.repository.get_graph()
442
parent_map = dict(((key, value) for key, value in
443
graph.iter_ancestry([last_revision]) if value is not None))
444
revision_graph = repository._strip_NULL_ghosts(parent_map)
445
revs = tsort.merge_sort(revision_graph, last_revision, None,
447
# Drop the sequence # before caching
448
self._merge_sorted_revisions_cache = [r[1:] for r in revs]
450
last_key = (last_revision,)
451
known_graph = self.repository.revisions.get_known_graph_ancestry(
453
self._merge_sorted_revisions_cache = known_graph.merge_sort(
450
455
filtered = self._filter_merge_sorted_revisions(
451
456
self._merge_sorted_revisions_cache, start_revision_id,
452
457
stop_revision_id, stop_rule)
462
467
"""Iterate over an inclusive range of sorted revisions."""
463
468
rev_iter = iter(merge_sorted_revisions)
464
469
if start_revision_id is not None:
465
for rev_id, depth, revno, end_of_merge in rev_iter:
470
for node in rev_iter:
471
rev_id = node.key[-1]
466
472
if rev_id != start_revision_id:
469
475
# The decision to include the start or not
470
476
# depends on the stop_rule if a stop is provided
472
iter([(rev_id, depth, revno, end_of_merge)]),
477
# so pop this node back into the iterator
478
rev_iter = chain(iter([node]), rev_iter)
475
480
if stop_revision_id is None:
476
for rev_id, depth, revno, end_of_merge in rev_iter:
477
yield rev_id, depth, revno, end_of_merge
482
for node in rev_iter:
483
rev_id = node.key[-1]
484
yield (rev_id, node.merge_depth, node.revno,
478
486
elif stop_rule == 'exclude':
479
for rev_id, depth, revno, end_of_merge in rev_iter:
487
for node in rev_iter:
488
rev_id = node.key[-1]
480
489
if rev_id == stop_revision_id:
482
yield rev_id, depth, revno, end_of_merge
491
yield (rev_id, node.merge_depth, node.revno,
483
493
elif stop_rule == 'include':
484
for rev_id, depth, revno, end_of_merge in rev_iter:
485
yield rev_id, depth, revno, end_of_merge
494
for node in rev_iter:
495
rev_id = node.key[-1]
496
yield (rev_id, node.merge_depth, node.revno,
486
498
if rev_id == stop_revision_id:
488
500
elif stop_rule == 'with-merges':
491
503
left_parent = stop_rev.parent_ids[0]
493
505
left_parent = _mod_revision.NULL_REVISION
494
for rev_id, depth, revno, end_of_merge in rev_iter:
506
# left_parent is the actual revision we want to stop logging at,
507
# since we want to show the merged revisions after the stop_rev too
508
reached_stop_revision_id = False
509
revision_id_whitelist = []
510
for node in rev_iter:
511
rev_id = node.key[-1]
495
512
if rev_id == left_parent:
513
# reached the left parent after the stop_revision
497
yield rev_id, depth, revno, end_of_merge
515
if (not reached_stop_revision_id or
516
rev_id in revision_id_whitelist):
517
yield (rev_id, node.merge_depth, node.revno,
519
if reached_stop_revision_id or rev_id == stop_revision_id:
520
# only do the merged revs of rev_id from now on
521
rev = self.repository.get_revision(rev_id)
523
reached_stop_revision_id = True
524
revision_id_whitelist.extend(rev.parent_ids)
499
526
raise ValueError('invalid stop_rule %r' % stop_rule)
1076
1103
params = ChangeBranchTipParams(
1077
1104
self, old_revno, new_revno, old_revid, new_revid)
1078
1105
for hook in hooks:
1081
except errors.TipChangeRejected:
1084
exc_info = sys.exc_info()
1085
hook_name = Branch.hooks.get_hook_name(hook)
1086
raise errors.HookFailed(
1087
'pre_change_branch_tip', hook_name, exc_info)
1089
1108
@needs_write_lock
1090
1109
def update(self):
1139
1158
revision_id: if not None, the revision history in the new branch will
1140
1159
be truncated to end with revision_id.
1161
if (repository_policy is not None and
1162
repository_policy.requires_stacking()):
1163
to_bzrdir._format.require_stacking(_skip_repo=True)
1142
1164
result = to_bzrdir.create_branch()
1143
1165
result.lock_write()
1222
1244
Callers will typically also want to check the repository.
1246
:param refs: Calculated refs for this branch as specified by
1247
branch._get_check_refs()
1224
1248
:return: A BranchCheckResult.
1226
ret = BranchCheckResult(self)
1227
mainline_parent_id = None
1250
result = BranchCheckResult(self)
1228
1251
last_revno, last_revision_id = self.last_revision_info()
1229
real_rev_history = []
1231
for revid in self.repository.iter_reverse_revision_history(
1233
real_rev_history.append(revid)
1234
except errors.RevisionNotPresent:
1235
ret.ghosts_in_mainline = True
1237
ret.ghosts_in_mainline = False
1238
real_rev_history.reverse()
1239
if len(real_rev_history) != last_revno:
1240
raise errors.BzrCheckError('revno does not match len(mainline)'
1241
' %s != %s' % (last_revno, len(real_rev_history)))
1242
# TODO: We should probably also check that real_rev_history actually
1243
# matches self.revision_history()
1244
for revision_id in real_rev_history:
1246
revision = self.repository.get_revision(revision_id)
1247
except errors.NoSuchRevision, e:
1248
raise errors.BzrCheckError("mainline revision {%s} not in repository"
1250
# In general the first entry on the revision history has no parents.
1251
# But it's not illegal for it to have parents listed; this can happen
1252
# in imports from Arch when the parents weren't reachable.
1253
if mainline_parent_id is not None:
1254
if mainline_parent_id not in revision.parent_ids:
1255
raise errors.BzrCheckError("previous revision {%s} not listed among "
1257
% (mainline_parent_id, revision_id))
1258
mainline_parent_id = revision_id
1252
actual_revno = refs[('lefthand-distance', last_revision_id)]
1253
if actual_revno != last_revno:
1254
result.errors.append(errors.BzrCheckError(
1255
'revno does not match len(mainline) %s != %s' % (
1256
last_revno, actual_revno)))
1257
# TODO: We should probably also check that self.revision_history
1258
# matches the repository for older branch formats.
1259
# If looking for the code that cross-checks repository parents against
1260
# the iter_reverse_revision_history output, that is now a repository
1261
1264
def _get_checkout_format(self):
1262
1265
"""Return the most suitable metadir for a checkout of this branch.
1287
1290
# clone call. Or something. 20090224 RBC/spiv.
1288
1291
if revision_id is None:
1289
1292
revision_id = self.last_revision()
1291
dir_to = self.bzrdir.clone_on_transport(to_transport,
1292
revision_id=revision_id, stacked_on=stacked_on,
1293
create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1294
except errors.FileExists:
1295
if not use_existing_dir:
1297
except errors.NoSuchFile:
1298
if not create_prefix:
1293
dir_to = self.bzrdir.clone_on_transport(to_transport,
1294
revision_id=revision_id, stacked_on=stacked_on,
1295
create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1300
1296
return dir_to.open_branch()
1302
1298
def create_checkout(self, to_location, revision_id=None,
1442
1438
"""Return the format for the branch object in a_bzrdir."""
1444
1440
transport = a_bzrdir.get_branch_transport(None)
1445
format_string = transport.get("format").read()
1441
format_string = transport.get_bytes("format")
1446
1442
return klass._formats[format_string]
1447
1443
except errors.NoSuchFile:
1448
raise errors.NotBranchError(path=transport.base)
1444
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1449
1445
except KeyError:
1450
1446
raise errors.UnknownFormatError(format=format_string, kind='branch')
1800
1796
_repository=a_bzrdir.find_repository(),
1801
1797
ignore_fallbacks=ignore_fallbacks)
1802
1798
except errors.NoSuchFile:
1803
raise errors.NotBranchError(path=transport.base)
1799
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1805
1801
def __init__(self):
1806
1802
super(BranchFormatMetadir, self).__init__()
1981
1977
def get_reference(self, a_bzrdir):
1982
1978
"""See BranchFormat.get_reference()."""
1983
1979
transport = a_bzrdir.get_branch_transport(None)
1984
return transport.get('location').read()
1980
return transport.get_bytes('location')
1986
1982
def set_reference(self, a_bzrdir, to_branch):
1987
1983
"""See BranchFormat.set_reference()."""
2075
2071
BranchFormat.register_format(__format6)
2076
2072
BranchFormat.register_format(__format7)
2077
2073
BranchFormat.register_format(__format8)
2078
BranchFormat.set_default_format(__format6)
2074
BranchFormat.set_default_format(__format7)
2079
2075
_legacy_formats = [BzrBranchFormat4(),
2081
2077
network_format_registry.register(
2082
2078
_legacy_formats[0].network_name(), _legacy_formats[0].__class__)
2085
class BzrBranch(Branch):
2081
class BzrBranch(Branch, _RelockDebugMixin):
2086
2082
"""A branch stored in the actual filesystem.
2088
2084
Note that it's "local" in the context of the filesystem; it doesn't
2134
2130
return self.control_files.is_locked()
2136
2132
def lock_write(self, token=None):
2133
if not self.is_locked():
2134
self._note_lock('w')
2137
2135
# All-in-one needs to always unlock/lock.
2138
2136
repo_control = getattr(self.repository, 'control_files', None)
2139
2137
if self.control_files == repo_control or not self.is_locked():
2138
self.repository._warn_if_deprecated(self)
2140
2139
self.repository.lock_write()
2141
2140
took_lock = True
2151
2150
def lock_read(self):
2151
if not self.is_locked():
2152
self._note_lock('r')
2152
2153
# All-in-one needs to always unlock/lock.
2153
2154
repo_control = getattr(self.repository, 'control_files', None)
2154
2155
if self.control_files == repo_control or not self.is_locked():
2156
self.repository._warn_if_deprecated(self)
2155
2157
self.repository.lock_read()
2156
2158
took_lock = True
2847
2850
:param verbose: Requests more detailed display of what was checked,
2850
note('checked branch %s format %s',
2852
self.branch._format)
2853
if self.ghosts_in_mainline:
2854
note('branch contains ghosts in mainline')
2853
note('checked branch %s format %s', self.branch.base,
2854
self.branch._format)
2855
for error in self.errors:
2856
note('found error:%s', error)
2857
2859
class Converter5to6(object):