103
101
def _open_hook(self):
104
102
"""Called by init to allow simpler extension of the base class."""
106
def _activate_fallback_location(self, url):
104
def _activate_fallback_location(self, url, lock_style):
107
105
"""Activate the branch/repository from url as a fallback repository."""
108
106
repo = self._get_fallback_repository(url)
109
if repo.has_same_location(self.repository):
110
raise errors.UnstackableLocationError(self.base, url)
107
if lock_style == 'write':
109
elif lock_style == 'read':
111
111
self.repository.add_fallback_repository(repo)
113
113
def break_lock(self):
129
129
raise errors.UnstackableRepositoryFormat(self.repository._format,
130
130
self.repository.base)
132
def _extend_partial_history(self, stop_index=None, stop_revision=None):
133
"""Extend the partial history to include a given index
135
If a stop_index is supplied, stop when that index has been reached.
136
If a stop_revision is supplied, stop when that revision is
137
encountered. Otherwise, stop when the beginning of history is
140
:param stop_index: The index which should be present. When it is
141
present, history extension will stop.
142
:param stop_revision: The revision id which should be present. When
143
it is encountered, history extension will stop.
145
if len(self._partial_revision_history_cache) == 0:
146
self._partial_revision_history_cache = [self.last_revision()]
147
repository._iter_for_revno(
148
self.repository, self._partial_revision_history_cache,
149
stop_index=stop_index, stop_revision=stop_revision)
150
if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
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)]
162
133
def open(base, _unsupported=False, possible_transports=None):
163
134
"""Open the branch rooted at base.
447
418
# start_revision_id.
448
419
if self._merge_sorted_revisions_cache is None:
449
420
last_revision = self.last_revision()
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(
421
graph = self.repository.get_graph()
422
parent_map = dict(((key, value) for key, value in
423
graph.iter_ancestry([last_revision]) if value is not None))
424
revision_graph = repository._strip_NULL_ghosts(parent_map)
425
revs = tsort.merge_sort(revision_graph, last_revision, None,
427
# Drop the sequence # before caching
428
self._merge_sorted_revisions_cache = [r[1:] for r in revs]
455
430
filtered = self._filter_merge_sorted_revisions(
456
431
self._merge_sorted_revisions_cache, start_revision_id,
457
432
stop_revision_id, stop_rule)
467
442
"""Iterate over an inclusive range of sorted revisions."""
468
443
rev_iter = iter(merge_sorted_revisions)
469
444
if start_revision_id is not None:
470
for node in rev_iter:
471
rev_id = node.key[-1]
445
for rev_id, depth, revno, end_of_merge in rev_iter:
472
446
if rev_id != start_revision_id:
475
449
# The decision to include the start or not
476
450
# depends on the stop_rule if a stop is provided
477
# so pop this node back into the iterator
478
rev_iter = chain(iter([node]), rev_iter)
452
iter([(rev_id, depth, revno, end_of_merge)]),
480
455
if stop_revision_id is None:
482
for node in rev_iter:
483
rev_id = node.key[-1]
484
yield (rev_id, node.merge_depth, node.revno,
456
for rev_id, depth, revno, end_of_merge in rev_iter:
457
yield rev_id, depth, revno, end_of_merge
486
458
elif stop_rule == 'exclude':
487
for node in rev_iter:
488
rev_id = node.key[-1]
459
for rev_id, depth, revno, end_of_merge in rev_iter:
489
460
if rev_id == stop_revision_id:
491
yield (rev_id, node.merge_depth, node.revno,
462
yield rev_id, depth, revno, end_of_merge
493
463
elif stop_rule == 'include':
494
for node in rev_iter:
495
rev_id = node.key[-1]
496
yield (rev_id, node.merge_depth, node.revno,
464
for rev_id, depth, revno, end_of_merge in rev_iter:
465
yield rev_id, depth, revno, end_of_merge
498
466
if rev_id == stop_revision_id:
500
468
elif stop_rule == 'with-merges':
503
471
left_parent = stop_rev.parent_ids[0]
505
473
left_parent = _mod_revision.NULL_REVISION
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]
474
for rev_id, depth, revno, end_of_merge in rev_iter:
512
475
if rev_id == left_parent:
513
# reached the left parent after the stop_revision
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)
477
yield rev_id, depth, revno, end_of_merge
526
479
raise ValueError('invalid stop_rule %r' % stop_rule)
550
503
raise errors.UpgradeRequired(self.base)
552
def set_append_revisions_only(self, enabled):
553
if not self._format.supports_set_append_revisions_only():
554
raise errors.UpgradeRequired(self.base)
559
self.get_config().set_user_option('append_revisions_only', value,
562
505
def set_reference_info(self, file_id, tree_path, branch_location):
563
506
"""Set the branch location to use for a tree reference."""
564
507
raise errors.UnsupportedOperation(self.set_reference_info, self)
700
640
except (errors.NotStacked, errors.UnstackableBranchFormat,
701
641
errors.UnstackableRepositoryFormat):
644
# XXX: Lock correctness - should unlock our old repo if we were
646
# repositories don't offer an interface to remove fallback
647
# repositories today; take the conceptually simpler option and just
649
self.repository = self.bzrdir.find_repository()
650
self.repository.lock_write()
651
# for every revision reference the branch has, ensure it is pulled
653
source_repository = self._get_fallback_repository(old_url)
654
for revision_id in chain([self.last_revision()],
655
self.tags.get_reverse_tag_dict()):
656
self.repository.fetch(source_repository, revision_id,
705
self._activate_fallback_location(url)
659
self._activate_fallback_location(url, 'write')
706
660
# write this out after the repository is stacked to avoid setting a
707
661
# stacked config that doesn't work.
708
662
self._set_config_location('stacked_on_location', url)
711
"""Change a branch to be unstacked, copying data as needed.
713
Don't call this directly, use set_stacked_on_url(None).
715
pb = ui.ui_factory.nested_progress_bar()
717
pb.update("Unstacking")
718
# The basic approach here is to fetch the tip of the branch,
719
# including all available ghosts, from the existing stacked
720
# repository into a new repository object without the fallbacks.
722
# XXX: See <https://launchpad.net/bugs/397286> - this may not be
723
# correct for CHKMap repostiories
724
old_repository = self.repository
725
if len(old_repository._fallback_repositories) != 1:
726
raise AssertionError("can't cope with fallback repositories "
727
"of %r" % (self.repository,))
728
# unlock it, including unlocking the fallback
729
old_repository.unlock()
730
old_repository.lock_read()
732
# Repositories don't offer an interface to remove fallback
733
# repositories today; take the conceptually simpler option and just
734
# reopen it. We reopen it starting from the URL so that we
735
# get a separate connection for RemoteRepositories and can
736
# stream from one of them to the other. This does mean doing
737
# separate SSH connection setup, but unstacking is not a
738
# common operation so it's tolerable.
739
new_bzrdir = bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
740
new_repository = new_bzrdir.find_repository()
741
self.repository = new_repository
742
if self.repository._fallback_repositories:
743
raise AssertionError("didn't expect %r to have "
744
"fallback_repositories"
745
% (self.repository,))
746
# this is not paired with an unlock because it's just restoring
747
# the previous state; the lock's released when set_stacked_on_url
749
self.repository.lock_write()
750
# XXX: If you unstack a branch while it has a working tree
751
# with a pending merge, the pending-merged revisions will no
752
# longer be present. You can (probably) revert and remerge.
754
# XXX: This only fetches up to the tip of the repository; it
755
# doesn't bring across any tags. That's fairly consistent
756
# with how branch works, but perhaps not ideal.
757
self.repository.fetch(old_repository,
758
revision_id=self.last_revision(),
761
old_repository.unlock()
765
665
def _set_tags_bytes(self, bytes):
766
666
"""Mirror method for _get_tags_bytes.
933
835
except ValueError:
934
836
raise errors.NoSuchRevision(self, revision_id)
937
838
def get_rev_id(self, revno, history=None):
938
839
"""Find the revision id of the specified revno."""
940
841
return _mod_revision.NULL_REVISION
941
last_revno, last_revid = self.last_revision_info()
942
if revno == last_revno:
944
if revno <= 0 or revno > last_revno:
843
history = self.revision_history()
844
if revno <= 0 or revno > len(history):
945
845
raise errors.NoSuchRevision(self, revno)
946
distance_from_last = last_revno - revno
947
if len(self._partial_revision_history_cache) <= distance_from_last:
948
self._extend_partial_history(distance_from_last)
949
return self._partial_revision_history_cache[distance_from_last]
846
return history[revno - 1]
951
848
@needs_write_lock
952
849
def pull(self, source, overwrite=False, stop_revision=None,
1187
1085
source_revno, source_revision_id = self.last_revision_info()
1188
1086
if revision_id is None:
1189
1087
revno, revision_id = source_revno, source_revision_id
1088
elif source_revision_id == revision_id:
1089
# we know the revno without needing to walk all of history
1090
revno = source_revno
1191
graph = self.repository.get_graph()
1193
revno = graph.find_distance_to_null(revision_id,
1194
[(source_revision_id, source_revno)])
1195
except errors.GhostRevisionsHaveNoRevno:
1196
# Default to 1, if we can't find anything else
1092
# To figure out the revno for a random revision, we need to build
1093
# the revision history, and count its length.
1094
# We don't care about the order, just how long it is.
1095
# Alternatively, we could start at the current location, and count
1096
# backwards. But there is no guarantee that we will find it since
1097
# it may be a merged revision.
1098
revno = len(list(self.repository.iter_reverse_revision_history(
1198
1100
destination.set_last_revision_info(revno, revision_id)
1200
1102
@needs_read_lock
1244
1146
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()
1248
1148
:return: A BranchCheckResult.
1250
result = BranchCheckResult(self)
1150
mainline_parent_id = None
1251
1151
last_revno, last_revision_id = self.last_revision_info()
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
1152
real_rev_history = list(self.repository.iter_reverse_revision_history(
1154
real_rev_history.reverse()
1155
if len(real_rev_history) != last_revno:
1156
raise errors.BzrCheckError('revno does not match len(mainline)'
1157
' %s != %s' % (last_revno, len(real_rev_history)))
1158
# TODO: We should probably also check that real_rev_history actually
1159
# matches self.revision_history()
1160
for revision_id in real_rev_history:
1162
revision = self.repository.get_revision(revision_id)
1163
except errors.NoSuchRevision, e:
1164
raise errors.BzrCheckError("mainline revision {%s} not in repository"
1166
# In general the first entry on the revision history has no parents.
1167
# But it's not illegal for it to have parents listed; this can happen
1168
# in imports from Arch when the parents weren't reachable.
1169
if mainline_parent_id is not None:
1170
if mainline_parent_id not in revision.parent_ids:
1171
raise errors.BzrCheckError("previous revision {%s} not listed among "
1173
% (mainline_parent_id, revision_id))
1174
mainline_parent_id = revision_id
1175
return BranchCheckResult(self)
1264
1177
def _get_checkout_format(self):
1265
1178
"""Return the most suitable metadir for a checkout of this branch.
1290
1203
# clone call. Or something. 20090224 RBC/spiv.
1291
1204
if revision_id is None:
1292
1205
revision_id = self.last_revision()
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)
1207
dir_to = self.bzrdir.clone_on_transport(to_transport,
1208
revision_id=revision_id, stacked_on=stacked_on,
1209
create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1210
except errors.FileExists:
1211
if not use_existing_dir:
1213
except errors.NoSuchFile:
1214
if not create_prefix:
1296
1216
return dir_to.open_branch()
1298
1218
def create_checkout(self, to_location, revision_id=None,
1438
1356
"""Return the format for the branch object in a_bzrdir."""
1440
1358
transport = a_bzrdir.get_branch_transport(None)
1441
format_string = transport.get_bytes("format")
1359
format_string = transport.get("format").read()
1442
1360
return klass._formats[format_string]
1443
1361
except errors.NoSuchFile:
1444
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1362
raise errors.NotBranchError(path=transport.base)
1445
1363
except KeyError:
1446
1364
raise errors.UnknownFormatError(format=format_string, kind='branch')
2071
1977
BranchFormat.register_format(__format6)
2072
1978
BranchFormat.register_format(__format7)
2073
1979
BranchFormat.register_format(__format8)
2074
BranchFormat.set_default_format(__format7)
1980
BranchFormat.set_default_format(__format6)
2075
1981
_legacy_formats = [BzrBranchFormat4(),
2077
1983
network_format_registry.register(
2078
1984
_legacy_formats[0].network_name(), _legacy_formats[0].__class__)
2081
class BzrBranch(Branch, _RelockDebugMixin):
1987
class BzrBranch(Branch):
2082
1988
"""A branch stored in the actual filesystem.
2084
1990
Note that it's "local" in the context of the filesystem; it doesn't
2467
2366
raise AssertionError(
2468
2367
"'transform_fallback_location' hook %s returned "
2469
2368
"None, not a URL." % hook_name)
2470
self._activate_fallback_location(url)
2369
self._activate_fallback_location(url, None)
2472
2371
def __init__(self, *args, **kwargs):
2473
2372
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
2474
2373
super(BzrBranch8, self).__init__(*args, **kwargs)
2475
2374
self._last_revision_info_cache = None
2375
self._partial_revision_history_cache = []
2476
2376
self._reference_info = None
2478
2378
def _clear_cached_state(self):
2479
2379
super(BzrBranch8, self)._clear_cached_state()
2480
2380
self._last_revision_info_cache = None
2381
self._partial_revision_history_cache = []
2481
2382
self._reference_info = None
2483
2384
def _last_revision_info(self):
2539
2440
self._extend_partial_history(stop_index=last_revno-1)
2540
2441
return list(reversed(self._partial_revision_history_cache))
2443
def _extend_partial_history(self, stop_index=None, stop_revision=None):
2444
"""Extend the partial history to include a given index
2446
If a stop_index is supplied, stop when that index has been reached.
2447
If a stop_revision is supplied, stop when that revision is
2448
encountered. Otherwise, stop when the beginning of history is
2451
:param stop_index: The index which should be present. When it is
2452
present, history extension will stop.
2453
:param revision_id: The revision id which should be present. When
2454
it is encountered, history extension will stop.
2456
repo = self.repository
2457
if len(self._partial_revision_history_cache) == 0:
2458
iterator = repo.iter_reverse_revision_history(self.last_revision())
2460
start_revision = self._partial_revision_history_cache[-1]
2461
iterator = repo.iter_reverse_revision_history(start_revision)
2462
#skip the last revision in the list
2463
next_revision = iterator.next()
2464
for revision_id in iterator:
2465
self._partial_revision_history_cache.append(revision_id)
2466
if (stop_index is not None and
2467
len(self._partial_revision_history_cache) > stop_index):
2469
if revision_id == stop_revision:
2542
2472
def _write_revision_history(self, history):
2543
2473
"""Factored out of set_revision_history.