50
from .decorators import (
47
53
from .hooks import Hooks
48
54
from .inter import InterObject
49
55
from .lock import LogicalLockResult
50
56
from .sixish import (
54
from .trace import mutter, mutter_callsite, note, is_quiet, warning
61
from .trace import mutter, mutter_callsite, note, is_quiet
57
64
class UnstackableBranchFormat(errors.BzrError):
59
66
_fmt = ("The branch '%(url)s'(%(format)s) is not a stackable format. "
60
"You will need to upgrade the branch to permit branch stacking.")
67
"You will need to upgrade the branch to permit branch stacking.")
62
69
def __init__(self, format, url):
63
70
errors.BzrError.__init__(self)
155
162
repository._iter_for_revno(
156
163
self.repository, self._partial_revision_history_cache,
157
164
stop_index=stop_index, stop_revision=stop_revision)
158
if self._partial_revision_history_cache[-1] == \
159
_mod_revision.NULL_REVISION:
165
if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
160
166
self._partial_revision_history_cache.pop()
162
168
def _get_check_refs(self):
174
180
For instance, if the branch is at URL/.bzr/branch,
175
181
Branch.open(URL) -> a Branch instance.
177
control = controldir.ControlDir.open(
178
base, possible_transports=possible_transports,
179
_unsupported=_unsupported)
180
return control.open_branch(
181
unsupported=_unsupported,
183
control = controldir.ControlDir.open(base,
184
possible_transports=possible_transports, _unsupported=_unsupported)
185
return control.open_branch(unsupported=_unsupported,
182
186
possible_transports=possible_transports)
185
189
def open_from_transport(transport, name=None, _unsupported=False,
186
possible_transports=None):
190
possible_transports=None):
187
191
"""Open the branch rooted at transport"""
188
control = controldir.ControlDir.open_from_transport(
189
transport, _unsupported)
190
return control.open_branch(
191
name=name, unsupported=_unsupported,
192
control = controldir.ControlDir.open_from_transport(transport, _unsupported)
193
return control.open_branch(name=name, unsupported=_unsupported,
192
194
possible_transports=possible_transports)
200
202
Basically we keep looking up until we find the control directory or
201
203
run into the root. If there isn't one, raises NotBranchError.
202
If there is one and it is either an unrecognised format or an
203
unsupported format, UnknownFormatError or UnsupportedFormatError are
204
raised. If there is one, it is returned, along with the unused portion
204
If there is one and it is either an unrecognised format or an unsupported
205
format, UnknownFormatError or UnsupportedFormatError are raised.
206
If there is one, it is returned, along with the unused portion of url.
207
control, relpath = controldir.ControlDir.open_containing(
208
url, possible_transports)
208
control, relpath = controldir.ControlDir.open_containing(url,
209
210
branch = control.open_branch(possible_transports=possible_transports)
210
211
return (branch, relpath)
277
278
# Silently fall back to local implicit nick if the master is
279
280
mutter("Could not connect to bound branch, "
280
"falling back to local nick.\n " + str(e))
281
"falling back to local nick.\n " + str(e))
281
282
return config.get_nickname()
283
284
def _set_nick(self, nick):
307
308
check_not_reserved_id = _mod_revision.check_not_reserved_id
308
309
# Do not include ghosts or graph origin in revision_history
309
while (current_rev_id in parents_map
310
and len(parents_map[current_rev_id]) > 0):
310
while (current_rev_id in parents_map and
311
len(parents_map[current_rev_id]) > 0):
311
312
check_not_reserved_id(current_rev_id)
312
313
new_history.append(current_rev_id)
313
314
current_rev_id = parents_map[current_rev_id][0]
365
366
provide a more efficient implementation.
367
368
if len(revno) == 1:
369
return self.get_rev_id(revno[0])
370
except errors.RevisionNotPresent as e:
371
raise errors.GhostRevisionsHaveNoRevno(revno[0], e.revision_id)
369
return self.get_rev_id(revno[0])
372
370
revision_id_to_revno = self.get_revision_id_to_revno_map()
373
371
revision_ids = [revision_id for revision_id, this_revno
374
372
in viewitems(revision_id_to_revno)
416
414
:return: A dictionary mapping revision_id => dotted revno.
417
415
This dictionary should not be modified by the caller.
419
if 'evil' in debug.debug_flags:
421
3, "get_revision_id_to_revno_map scales with ancestry.")
422
417
with self.lock_read():
423
418
if self._revision_id_to_revno_cache is not None:
424
419
mapping = self._revision_id_to_revno_cache
426
421
mapping = self._gen_revno_map()
427
422
self._cache_revision_id_to_revno(mapping)
428
# TODO: jam 20070417 Since this is being cached, should we be
430
# I would rather not, and instead just declare that users should
431
# not modify the return value.
423
# TODO: jam 20070417 Since this is being cached, should we be returning
425
# I would rather not, and instead just declare that users should not
426
# modify the return value.
434
429
def _gen_revno_map(self):
442
437
:return: A dictionary mapping revision_id => dotted revno.
444
revision_id_to_revno = {
445
rev_id: revno for rev_id, depth, revno, end_of_merge
446
in self.iter_merge_sorted_revisions()}
439
revision_id_to_revno = dict((rev_id, revno)
440
for rev_id, depth, revno, end_of_merge
441
in self.iter_merge_sorted_revisions())
447
442
return revision_id_to_revno
449
444
def iter_merge_sorted_revisions(self, start_revision_id=None,
450
stop_revision_id=None,
451
stop_rule='exclude', direction='reverse'):
445
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
452
446
"""Walk the revisions for a branch in merge sorted order.
454
448
Merge sorted order is the output from a merge-aware,
466
460
* 'include' - the stop revision is the last item in the result
467
461
* 'with-merges' - include the stop revision and all of its
468
462
merged revisions in the result
469
* 'with-merges-without-common-ancestry' - filter out revisions
463
* 'with-merges-without-common-ancestry' - filter out revisions
470
464
that are in both ancestries
471
465
:param direction: either 'reverse' or 'forward':
515
509
raise ValueError('invalid direction %r' % direction)
517
511
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
518
start_revision_id, stop_revision_id,
512
start_revision_id, stop_revision_id, stop_rule):
520
513
"""Iterate over an inclusive range of sorted revisions."""
521
514
rev_iter = iter(merge_sorted_revisions)
522
515
if start_revision_id is not None:
577
570
if rev_id == left_parent:
578
571
# reached the left parent after the stop_revision
580
if (not reached_stop_revision_id
581
or rev_id in revision_id_whitelist):
573
if (not reached_stop_revision_id or
574
rev_id in revision_id_whitelist):
582
575
yield (rev_id, node.merge_depth, node.revno,
584
577
if reached_stop_revision_id or rev_id == stop_revision_id:
585
578
# only do the merged revs of rev_id from now on
586
579
rev = self.repository.get_revision(rev_id)
651
640
"""Tell this branch object to release the physical lock when this
652
641
object is unlocked, even if it didn't originally acquire it.
654
If lock_write doesn't return a token, then this method is not
643
If lock_write doesn't return a token, then this method is not supported.
657
645
self.control_files.dont_leave_in_place()
676
664
raise errors.UpgradeRequired(self.user_url)
677
665
self.get_config_stack().set('append_revisions_only', enabled)
679
def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
667
def set_reference_info(self, tree_path, branch_location, file_id=None):
668
"""Set the branch location to use for a tree reference."""
669
raise errors.UnsupportedOperation(self.set_reference_info, self)
671
def get_reference_info(self, path):
672
"""Get the tree_path and branch_location for a tree reference."""
673
raise errors.UnsupportedOperation(self.get_reference_info, self)
675
def fetch(self, from_branch, last_revision=None, limit=None):
680
676
"""Copy revisions from from_branch into this branch.
682
678
:param from_branch: Where to copy from.
683
:param stop_revision: What revision to stop at (None for at the end
679
:param last_revision: What revision to stop at (None for at the end
685
681
:param limit: Optional rough limit of revisions to fetch
688
684
with self.lock_write():
689
685
return InterBranch.get(from_branch, self).fetch(
690
stop_revision, limit=limit, lossy=lossy)
686
last_revision, limit=limit)
692
688
def get_bound_location(self):
693
689
"""Return the URL of the branch we are bound to.
715
711
:param revprops: Optional dictionary of revision properties.
716
712
:param revision_id: Optional revision id.
717
713
:param lossy: Whether to discard data that can not be natively
718
represented, when pushing to a foreign VCS
714
represented, when pushing to a foreign VCS
721
717
if config_stack is None:
722
718
config_stack = self.get_config_stack()
724
return self.repository.get_commit_builder(
725
self, parents, config_stack, timestamp, timezone, committer,
726
revprops, revision_id, lossy)
720
return self.repository.get_commit_builder(self, parents, config_stack,
721
timestamp, timezone, committer, revprops, revision_id,
728
724
def get_master_branch(self, possible_transports=None):
729
725
"""Return the branch we are bound to.
768
764
if not graph.is_ancestor(last_rev, revision_id):
769
765
# our previous tip is not merged into stop_revision
770
766
raise errors.DivergedBranches(self, other_branch)
771
revno = graph.find_distance_to_null(
772
revision_id, known_revision_ids)
767
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
773
768
self.set_last_revision_info(revno, revision_id)
775
770
def set_parent(self, url):
779
774
# FIXUP this and get_parent in a future branch format bump:
780
775
# read and rewrite the file. RBC 20060125
781
776
if url is not None:
782
if isinstance(url, text_type):
777
# TODO(jelmer): Clean this up for pad.lv/1696545
778
if isinstance(url, text_type) and sys.version_info[0] == 2:
780
url = url.encode('ascii')
785
781
except UnicodeEncodeError:
786
raise urlutils.InvalidURL(
787
url, "Urls must be 7-bit ascii, "
782
raise urlutils.InvalidURL(url,
783
"Urls must be 7-bit ascii, "
788
784
"use breezy.urlutils.escape")
789
785
url = urlutils.relative_url(self.base, url)
790
786
with self.lock_write():
801
797
if not self._format.supports_stacking():
802
798
raise UnstackableBranchFormat(self._format, self.user_url)
803
799
with self.lock_write():
804
# XXX: Changing from one fallback repository to another does not
805
# check that all the data you need is present in the new fallback.
800
# XXX: Changing from one fallback repository to another does not check
801
# that all the data you need is present in the new fallback.
806
802
# Possibly it should.
807
803
self._check_stackable_repo()
810
self.get_stacked_on_url()
806
old_url = self.get_stacked_on_url()
811
807
except (errors.NotStacked, UnstackableBranchFormat,
812
errors.UnstackableRepositoryFormat):
808
errors.UnstackableRepositoryFormat):
816
self._activate_fallback_location(
817
url, possible_transports=[self.controldir.root_transport])
812
self._activate_fallback_location(url,
813
possible_transports=[self.controldir.root_transport])
818
814
# write this out after the repository is stacked to avoid setting a
819
815
# stacked config that doesn't work.
820
816
self._set_config_location('stacked_on_location', url)
828
824
pb.update(gettext("Unstacking"))
829
825
# The basic approach here is to fetch the tip of the branch,
830
826
# including all available ghosts, from the existing stacked
831
# repository into a new repository object without the fallbacks.
827
# repository into a new repository object without the fallbacks.
833
829
# XXX: See <https://launchpad.net/bugs/397286> - this may not be
834
830
# correct for CHKMap repostiories
835
831
old_repository = self.repository
836
832
if len(old_repository._fallback_repositories) != 1:
837
raise AssertionError(
838
"can't cope with fallback repositories "
839
"of %r (fallbacks: %r)" % (
840
old_repository, old_repository._fallback_repositories))
833
raise AssertionError("can't cope with fallback repositories "
834
"of %r (fallbacks: %r)" % (old_repository,
835
old_repository._fallback_repositories))
841
836
# Open the new repository object.
842
837
# Repositories don't offer an interface to remove fallback
843
838
# repositories today; take the conceptually simpler option and just
850
845
self.controldir.root_transport.base)
851
846
new_repository = new_bzrdir.find_repository()
852
847
if new_repository._fallback_repositories:
853
raise AssertionError(
854
"didn't expect %r to have fallback_repositories"
848
raise AssertionError("didn't expect %r to have "
849
"fallback_repositories"
855
850
% (self.repository,))
856
851
# Replace self.repository with the new repository.
857
852
# Do our best to transfer the lock state (i.e. lock-tokens and
884
879
if old_lock_count == 0:
885
880
raise AssertionError(
886
881
'old_repository should have been locked at least once.')
887
for i in range(old_lock_count - 1):
882
for i in range(old_lock_count-1):
888
883
self.repository.lock_write()
889
884
# Fetch from the old repository into the new.
890
885
with old_repository.lock_read():
895
890
tags_to_fetch = set(self.tags.get_reverse_tag_dict())
896
891
except errors.TagsNotSupported:
897
892
tags_to_fetch = set()
898
fetch_spec = vf_search.NotInOtherForRevs(
899
self.repository, old_repository,
900
required_ids=[self.last_revision()],
893
fetch_spec = vf_search.NotInOtherForRevs(self.repository,
894
old_repository, required_ids=[self.last_revision()],
901
895
if_present_ids=tags_to_fetch, find_ghosts=True).execute()
902
896
self.repository.fetch(old_repository, fetch_spec=fetch_spec)
986
980
with self.lock_read():
987
981
if self._last_revision_info_cache is None:
988
self._last_revision_info_cache = (
989
self._read_last_revision_info())
982
self._last_revision_info_cache = self._read_last_revision_info()
990
983
return self._last_revision_info_cache
992
985
def _read_last_revision_info(self):
1047
1040
:returns: PullResult instance
1049
return InterBranch.get(source, self).pull(
1050
overwrite=overwrite, stop_revision=stop_revision,
1042
return InterBranch.get(source, self).pull(overwrite=overwrite,
1043
stop_revision=stop_revision,
1051
1044
possible_transports=possible_transports, *args, **kwargs)
1053
1046
def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1055
1048
"""Mirror this branch into target.
1057
1050
This branch is considered to be 'local', having low latency.
1059
return InterBranch.get(self, target).push(
1060
overwrite, stop_revision, lossy, *args, **kwargs)
1052
return InterBranch.get(self, target).push(overwrite, stop_revision,
1053
lossy, *args, **kwargs)
1062
1055
def basis_tree(self):
1063
1056
"""Return `Tree` object for last revision."""
1076
1069
# This is an old-format absolute path to a local branch
1077
1070
# turn it into a url
1078
1071
if parent.startswith('/'):
1079
parent = urlutils.local_path_to_url(parent)
1072
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1081
1074
return urlutils.join(self.base[:-1], parent)
1082
except urlutils.InvalidURLJoin:
1075
except urlutils.InvalidURLJoin as e:
1083
1076
raise errors.InaccessibleParent(parent, self.user_url)
1085
1078
def _get_parent_location(self):
1194
1187
if revno < 1 or revno > self.revno():
1195
1188
raise errors.InvalidRevisionNumber(revno)
1197
def clone(self, to_controldir, revision_id=None, name=None,
1198
repository_policy=None, tag_selector=None):
1190
def clone(self, to_controldir, revision_id=None, repository_policy=None):
1199
1191
"""Clone this branch into to_controldir preserving all semantic values.
1201
1193
Most API users will want 'create_clone_on_transport', which creates a
1204
1196
revision_id: if not None, the revision history in the new branch will
1205
1197
be truncated to end with revision_id.
1207
result = to_controldir.create_branch(name=name)
1199
result = to_controldir.create_branch()
1208
1200
with self.lock_read(), result.lock_write():
1209
1201
if repository_policy is not None:
1210
1202
repository_policy.configure_branch(result)
1211
self.copy_content_into(
1212
result, revision_id=revision_id, tag_selector=tag_selector)
1203
self.copy_content_into(result, revision_id=revision_id)
1215
1206
def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1216
repository=None, lossy=False, tag_selector=None):
1207
repository=None, lossy=False):
1217
1208
"""Create a new line of development from the branch, into to_controldir.
1219
1210
to_controldir controls the branch format.
1221
1212
revision_id: if not None, the revision history in the new branch will
1222
1213
be truncated to end with revision_id.
1224
if (repository_policy is not None
1225
and repository_policy.requires_stacking()):
1215
if (repository_policy is not None and
1216
repository_policy.requires_stacking()):
1226
1217
to_controldir._format.require_stacking(_skip_repo=True)
1227
1218
result = to_controldir.create_branch(repository=repository)
1230
1221
with self.lock_read(), result.lock_write():
1231
1222
if repository_policy is not None:
1232
1223
repository_policy.configure_branch(result)
1233
self.copy_content_into(
1234
result, revision_id=revision_id, tag_selector=tag_selector)
1224
self.copy_content_into(result, revision_id=revision_id)
1235
1225
master_url = self.get_bound_location()
1236
1226
if master_url is None:
1237
1227
result.set_parent(self.user_url)
1258
1248
graph = self.repository.get_graph()
1260
revno = graph.find_distance_to_null(
1261
revision_id, [(source_revision_id, source_revno)])
1250
revno = graph.find_distance_to_null(revision_id,
1251
[(source_revision_id, source_revno)])
1262
1252
except errors.GhostRevisionsHaveNoRevno:
1263
1253
# Default to 1, if we can't find anything else
1265
1255
destination.set_last_revision_info(revno, revision_id)
1267
def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1257
def copy_content_into(self, destination, revision_id=None):
1268
1258
"""Copy the content of self into destination.
1270
1260
revision_id: if not None, the revision history in the new branch will
1271
1261
be truncated to end with revision_id.
1272
tag_selector: Optional callback that receives a tag name
1273
and should return a boolean to indicate whether a tag should be copied
1275
1263
return InterBranch.get(self, destination).copy_content_into(
1276
revision_id=revision_id, tag_selector=tag_selector)
1264
revision_id=revision_id)
1278
1266
def update_references(self, target):
1279
if not self._format.supports_reference_locations:
1281
return InterBranch.get(self, target).update_references()
1267
if not getattr(self._format, 'supports_reference_locations', False):
1269
reference_dict = self._get_all_reference_info()
1270
if len(reference_dict) == 0:
1272
old_base = self.base
1273
new_base = target.base
1274
target_reference_dict = target._get_all_reference_info()
1275
for tree_path, (branch_location, file_id) in viewitems(reference_dict):
1276
branch_location = urlutils.rebase_url(branch_location,
1278
target_reference_dict.setdefault(
1279
tree_path, (branch_location, file_id))
1280
target._set_all_reference_info(target_reference_dict)
1283
1282
def check(self, refs):
1284
1283
"""Check consistency of the branch.
1300
1299
if actual_revno != last_revno:
1301
1300
result.errors.append(errors.BzrCheckError(
1302
1301
'revno does not match len(mainline) %s != %s' % (
1303
last_revno, actual_revno)))
1302
last_revno, actual_revno)))
1304
1303
# TODO: We should probably also check that self.revision_history
1305
1304
# matches the repository for older branch formats.
1306
# If looking for the code that cross-checks repository parents
1307
# against the Graph.iter_lefthand_ancestry output, that is now a
1308
# repository specific check.
1305
# If looking for the code that cross-checks repository parents against
1306
# the Graph.iter_lefthand_ancestry output, that is now a repository
1311
1310
def _get_checkout_format(self, lightweight=False):
1319
1318
def create_clone_on_transport(self, to_transport, revision_id=None,
1320
stacked_on=None, create_prefix=False,
1321
use_existing_dir=False, no_tree=None,
1319
stacked_on=None, create_prefix=False, use_existing_dir=False,
1323
1321
"""Create a clone of this branch and its bzrdir.
1325
1323
:param to_transport: The transport to clone onto.
1333
1331
# XXX: Fix the bzrdir API to allow getting the branch back from the
1334
1332
# clone call. Or something. 20090224 RBC/spiv.
1335
# XXX: Should this perhaps clone colocated branches as well,
1333
# XXX: Should this perhaps clone colocated branches as well,
1336
1334
# rather than just the default branch? 20100319 JRV
1337
1335
if revision_id is None:
1338
1336
revision_id = self.last_revision()
1339
dir_to = self.controldir.clone_on_transport(
1340
to_transport, revision_id=revision_id, stacked_on=stacked_on,
1337
dir_to = self.controldir.clone_on_transport(to_transport,
1338
revision_id=revision_id, stacked_on=stacked_on,
1341
1339
create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1342
no_tree=no_tree, tag_selector=tag_selector)
1343
1341
return dir_to.open_branch()
1345
1343
def create_checkout(self, to_location, revision_id=None,
1346
1344
lightweight=False, accelerator_tree=None,
1347
hardlink=False, recurse_nested=True):
1348
1346
"""Create a checkout of a branch.
1350
1348
:param to_location: The url to produce the checkout at
1377
1374
raise errors.AlreadyControlDirError(t.base)
1378
if (checkout.control_transport.base
1379
== self.controldir.control_transport.base):
1375
if checkout.control_transport.base == self.controldir.control_transport.base:
1380
1376
# When checking out to the same control directory,
1381
1377
# always create a lightweight checkout
1382
1378
lightweight = True
1385
1381
from_branch = checkout.set_branch_reference(target_branch=self)
1387
1383
policy = checkout.determine_repository_policy()
1388
policy.acquire_repository()
1384
repo = policy.acquire_repository()[0]
1389
1385
checkout_branch = checkout.create_branch()
1390
1386
checkout_branch.bind(self)
1391
1387
# pull up to the specified revision_id to set the initial
1398
1394
hardlink=hardlink)
1399
1395
basis_tree = tree.basis_tree()
1400
1396
with basis_tree.lock_read():
1401
for path in basis_tree.iter_references():
1402
reference_parent = tree.reference_parent(path)
1403
if reference_parent is None:
1404
warning('Branch location for %s unknown.', path)
1406
reference_parent.create_checkout(
1408
basis_tree.get_reference_revision(path), lightweight)
1397
for path, file_id in basis_tree.iter_references():
1398
reference_parent = self.reference_parent(path, file_id)
1399
reference_parent.create_checkout(tree.abspath(path),
1400
basis_tree.get_reference_revision(path, file_id),
1411
1404
def reconcile(self, thorough=True):
1412
"""Make sure the data stored in this branch is consistent.
1414
:return: A `ReconcileResult` object.
1405
"""Make sure the data stored in this branch is consistent."""
1406
from breezy.reconcile import BranchReconciler
1407
with self.lock_write():
1408
reconciler = BranchReconciler(self, thorough=thorough)
1409
reconciler.reconcile()
1412
def reference_parent(self, path, file_id=None, possible_transports=None):
1413
"""Return the parent branch for a tree-reference file_id
1415
:param path: The path of the file_id in the tree
1416
:param file_id: Optional file_id of the tree reference
1417
:return: A branch associated with the file_id
1416
raise NotImplementedError(self.reconcile)
1419
# FIXME should provide multiple branches, based on config
1420
return Branch.open(self.controldir.root_transport.clone(path).base,
1421
possible_transports=possible_transports)
1418
1423
def supports_tags(self):
1419
1424
return self._format.supports_tags()
1600
1605
raise NotImplementedError(self.network_name)
1602
1607
def open(self, controldir, name=None, _found=False, ignore_fallbacks=False,
1603
found_repository=None, possible_transports=None):
1608
found_repository=None, possible_transports=None):
1604
1609
"""Return the branch object for controldir.
1606
1611
:param controldir: A ControlDir that contains a branch.
1664
1665
Hooks.__init__(self, "breezy.branch", "Branch.hooks")
1666
self.add_hook('open',
1667
1667
"Called with the Branch object that has been opened after a "
1668
1668
"branch is opened.", (1, 8))
1669
self.add_hook('post_push',
1671
1670
"Called after a push operation completes. post_push is called "
1672
"with a breezy.branch.BranchPushResult object and only runs in "
1673
"the bzr client.", (0, 15))
1671
"with a breezy.branch.BranchPushResult object and only runs in the "
1672
"bzr client.", (0, 15))
1673
self.add_hook('post_pull',
1676
1674
"Called after a pull operation completes. post_pull is called "
1677
1675
"with a breezy.branch.PullResult object and only runs in the "
1678
1676
"bzr client.", (0, 15))
1677
self.add_hook('pre_commit',
1681
1678
"Called after a commit is calculated but before it is "
1682
1679
"completed. pre_commit is called with (local, master, old_revno, "
1683
1680
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1687
1684
" future_tree is an in-memory tree obtained from "
1688
1685
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1689
1686
"tree.", (0, 91))
1687
self.add_hook('post_commit',
1692
1688
"Called in the bzr client after a commit has completed. "
1693
1689
"post_commit is called with (local, master, old_revno, old_revid, "
1694
1690
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1695
1691
"commit to a branch.", (0, 15))
1692
self.add_hook('post_uncommit',
1698
1693
"Called in the bzr client after an uncommit completes. "
1699
1694
"post_uncommit is called with (local, master, old_revno, "
1700
1695
"old_revid, new_revno, new_revid) where local is the local branch "
1701
1696
"or None, master is the target branch, and an empty branch "
1702
1697
"receives new_revno of 0, new_revid of None.", (0, 15))
1704
'pre_change_branch_tip',
1698
self.add_hook('pre_change_branch_tip',
1705
1699
"Called in bzr client and server before a change to the tip of a "
1706
1700
"branch is made. pre_change_branch_tip is called with a "
1707
1701
"breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1708
1702
"commit, uncommit will all trigger this hook.", (1, 6))
1710
'post_change_branch_tip',
1703
self.add_hook('post_change_branch_tip',
1711
1704
"Called in bzr client and server after a change to the tip of a "
1712
1705
"branch is made. post_change_branch_tip is called with a "
1713
1706
"breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1714
1707
"commit, uncommit will all trigger this hook.", (1, 4))
1716
'transform_fallback_location',
1708
self.add_hook('transform_fallback_location',
1717
1709
"Called when a stacked branch is activating its fallback "
1718
1710
"locations. transform_fallback_location is called with (branch, "
1719
1711
"url), and should return a new url. Returning the same url "
1725
1717
"multiple hooks installed for transform_fallback_location, "
1726
1718
"all are called with the url returned from the previous hook."
1727
1719
"The order is however undefined.", (1, 9))
1729
'automatic_tag_name',
1720
self.add_hook('automatic_tag_name',
1730
1721
"Called to determine an automatic tag name for a revision. "
1731
1722
"automatic_tag_name is called with (branch, revision_id) and "
1732
1723
"should return a tag name or None if no tag name could be "
1733
1724
"determined. The first non-None tag name returned will be used.",
1726
self.add_hook('post_branch_init',
1737
1727
"Called after new branch initialization completes. "
1738
1728
"post_branch_init is called with a "
1739
1729
"breezy.branch.BranchInitHookParams. "
1740
1730
"Note that init, branch and checkout (both heavyweight and "
1741
1731
"lightweight) will all trigger this hook.", (2, 2))
1732
self.add_hook('post_switch',
1744
1733
"Called after a checkout switches branch. "
1745
1734
"post_switch is called with a "
1746
1735
"breezy.branch.SwitchHookParams.", (2, 2))
1749
1739
# install the default hooks into the Branch class.
1750
1740
Branch.hooks = BranchHooks()
1856
1846
return self.__dict__ == other.__dict__
1858
1848
def __repr__(self):
1859
return "<%s for %s to (%s, %s)>" % (
1860
self.__class__.__name__, self.control_dir, self.to_branch,
1849
return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
1850
self.control_dir, self.to_branch,
1861
1851
self.revision_id)
1872
1862
def get_default(self):
1873
1863
"""Return the current default format."""
1874
if (self._default_format_key is not None
1875
and self._default_format is None):
1864
if (self._default_format_key is not None and
1865
self._default_format is None):
1876
1866
self._default_format = self.get(self._default_format_key)
1877
1867
return self._default_format
2005
1995
tag_updates = getattr(self, "tag_updates", None)
2006
1996
if not is_quiet():
2007
1997
if self.old_revid != self.new_revid:
2008
if self.new_revno is not None:
2009
note(gettext('Pushed up to revision %d.'),
2012
note(gettext('Pushed up to revision id %s.'),
2013
self.new_revid.decode('utf-8'))
1998
note(gettext('Pushed up to revision %d.') % self.new_revno)
2014
1999
if tag_updates:
2015
note(ngettext('%d tag updated.', '%d tags updated.',
2016
len(tag_updates)) % len(tag_updates))
2000
note(ngettext('%d tag updated.', '%d tags updated.', len(tag_updates)) % len(tag_updates))
2017
2001
if self.old_revid == self.new_revid and not tag_updates:
2018
2002
if not tag_conflicts:
2019
2003
note(gettext('No new revisions or tags to push.'))
2041
2025
note(gettext('checked branch {0} format {1}').format(
2042
self.branch.user_url, self.branch._format))
2026
self.branch.user_url, self.branch._format))
2043
2027
for error in self.errors:
2044
2028
note(gettext('found error:%s'), error)
2066
2050
raise NotImplementedError(klass._get_branch_formats_to_test)
2068
2052
def pull(self, overwrite=False, stop_revision=None,
2069
possible_transports=None, local=False, tag_selector=None):
2053
possible_transports=None, local=False):
2070
2054
"""Mirror source into target branch.
2072
2056
The target branch is considered to be 'local', having low latency.
2076
2060
raise NotImplementedError(self.pull)
2078
2062
def push(self, overwrite=False, stop_revision=None, lossy=False,
2079
_override_hook_source_branch=None, tag_selector=None):
2063
_override_hook_source_branch=None):
2080
2064
"""Mirror the source branch into the target branch.
2082
2066
The source branch is considered to be 'local', having low latency.
2084
2068
raise NotImplementedError(self.push)
2086
def copy_content_into(self, revision_id=None, tag_selector=None):
2070
def copy_content_into(self, revision_id=None):
2087
2071
"""Copy the content of source into target
2090
if not None, the revision history in the new branch will
2091
be truncated to end with revision_id.
2092
:param tag_selector: Optional callback that can decide
2093
to copy or not copy tags.
2073
revision_id: if not None, the revision history in the new branch will
2074
be truncated to end with revision_id.
2095
2076
raise NotImplementedError(self.copy_content_into)
2097
def fetch(self, stop_revision=None, limit=None, lossy=False):
2078
def fetch(self, stop_revision=None, limit=None):
2098
2079
"""Fetch revisions.
2100
2081
:param stop_revision: Last revision to fetch
2101
2082
:param limit: Optional rough limit of revisions to fetch
2102
:return: FetchResult object
2104
2084
raise NotImplementedError(self.fetch)
2106
def update_references(self):
2107
"""Import reference information from source to target.
2109
raise NotImplementedError(self.update_references)
2112
2087
def _fix_overwrite_type(overwrite):
2113
2088
if isinstance(overwrite, bool):
2137
2112
return format._custom_format
2140
def copy_content_into(self, revision_id=None, tag_selector=None):
2115
def copy_content_into(self, revision_id=None):
2141
2116
"""Copy the content of source into target
2143
2118
revision_id: if not None, the revision history in the new branch will
2144
2119
be truncated to end with revision_id.
2146
2121
with self.source.lock_read(), self.target.lock_write():
2122
self.source.update_references(self.target)
2147
2123
self.source._synchronize_history(self.target, revision_id)
2148
self.update_references()
2150
2125
parent = self.source.get_parent()
2151
2126
except errors.InaccessibleParent as e:
2155
2130
self.target.set_parent(parent)
2156
2131
if self.source._push_should_merge_tags():
2157
self.source.tags.merge_to(self.target.tags, selector=tag_selector)
2132
self.source.tags.merge_to(self.target.tags)
2159
def fetch(self, stop_revision=None, limit=None, lossy=False):
2134
def fetch(self, stop_revision=None, limit=None):
2160
2135
if self.target.base == self.source.base:
2162
2137
with self.source.lock_read(), self.target.lock_write():
2165
2140
fetch_spec_factory.source_branch_stop_revision_id = stop_revision
2166
2141
fetch_spec_factory.source_repo = self.source.repository
2167
2142
fetch_spec_factory.target_repo = self.target.repository
2168
fetch_spec_factory.target_repo_kind = (
2169
fetch.TargetRepoKinds.PREEXISTING)
2143
fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
2170
2144
fetch_spec_factory.limit = limit
2171
2145
fetch_spec = fetch_spec_factory.make_fetch_spec()
2172
2146
return self.target.repository.fetch(
2173
self.source.repository,
2175
fetch_spec=fetch_spec)
2147
self.source.repository,
2148
fetch_spec=fetch_spec)
2177
2150
def _update_revisions(self, stop_revision=None, overwrite=False,
2179
2152
with self.source.lock_read(), self.target.lock_write():
2180
2153
other_revno, other_last_revision = self.source.last_revision_info()
2181
stop_revno = None # unknown
2154
stop_revno = None # unknown
2182
2155
if stop_revision is None:
2183
2156
stop_revision = other_last_revision
2184
2157
if _mod_revision.is_null(stop_revision):
2207
2180
if graph is None:
2208
2181
graph = self.target.repository.get_graph()
2209
2182
this_revno, this_last_revision = \
2210
self.target.last_revision_info()
2211
stop_revno = graph.find_distance_to_null(
2212
stop_revision, [(other_last_revision, other_revno),
2213
(this_last_revision, this_revno)])
2183
self.target.last_revision_info()
2184
stop_revno = graph.find_distance_to_null(stop_revision,
2185
[(other_last_revision, other_revno),
2186
(this_last_revision, this_revno)])
2214
2187
self.target.set_last_revision_info(stop_revno, stop_revision)
2216
2189
def pull(self, overwrite=False, stop_revision=None,
2217
2190
possible_transports=None, run_hooks=True,
2218
_override_hook_target=None, local=False,
2191
_override_hook_target=None, local=False):
2220
2192
"""Pull from source into self, updating my master if any.
2222
2194
:param run_hooks: Private parameter - if false, this branch
2223
2195
is being called because it's the master of the primary branch,
2224
2196
so it should not run its hooks.
2226
with cleanup.ExitStack() as exit_stack:
2227
exit_stack.enter_context(self.target.lock_write())
2198
with self.target.lock_write():
2228
2199
bound_location = self.target.get_bound_location()
2229
2200
if local and not bound_location:
2230
2201
raise errors.LocalRequiresBoundBranch()
2241
2212
source_is_master = False
2242
2213
if not local and bound_location and not source_is_master:
2243
2214
# not pulling from master, so we need to update master.
2244
master_branch = self.target.get_master_branch(
2245
possible_transports)
2246
exit_stack.enter_context(master_branch.lock_write())
2248
# pull from source into master.
2250
self.source, overwrite, stop_revision, run_hooks=False,
2251
tag_selector=tag_selector)
2253
overwrite, stop_revision, _hook_master=master_branch,
2254
run_hooks=run_hooks,
2255
_override_hook_target=_override_hook_target,
2256
merge_tags_to_master=not source_is_master,
2257
tag_selector=tag_selector)
2215
master_branch = self.target.get_master_branch(possible_transports)
2216
master_branch.lock_write()
2219
# pull from source into master.
2220
master_branch.pull(self.source, overwrite, stop_revision,
2222
return self._pull(overwrite,
2223
stop_revision, _hook_master=master_branch,
2224
run_hooks=run_hooks,
2225
_override_hook_target=_override_hook_target,
2226
merge_tags_to_master=not source_is_master)
2229
master_branch.unlock()
2259
2231
def push(self, overwrite=False, stop_revision=None, lossy=False,
2260
_override_hook_source_branch=None, tag_selector=None):
2232
_override_hook_source_branch=None):
2261
2233
"""See InterBranch.push.
2263
2235
This is the basic concrete implementation of push()
2283
2254
if bound_location and self.target.base != bound_location:
2284
2255
# there is a master branch.
2286
# XXX: Why the second check? Is it even supported for a branch
2287
# to be bound to itself? -- mbp 20070507
2257
# XXX: Why the second check? Is it even supported for a branch to
2258
# be bound to itself? -- mbp 20070507
2288
2259
master_branch = self.target.get_master_branch()
2289
2260
with master_branch.lock_write():
2290
2261
# push into the master from the source branch.
2291
2262
master_inter = InterBranch.get(self.source, master_branch)
2292
master_inter._basic_push(
2293
overwrite, stop_revision, tag_selector=tag_selector)
2294
# and push into the target branch from the source. Note
2295
# that we push from the source branch again, because it's
2296
# considered the highest bandwidth repository.
2297
result = self._basic_push(
2298
overwrite, stop_revision, tag_selector=tag_selector)
2263
master_inter._basic_push(overwrite, stop_revision)
2264
# and push into the target branch from the source. Note that
2265
# we push from the source branch again, because it's considered
2266
# the highest bandwidth repository.
2267
result = self._basic_push(overwrite, stop_revision)
2299
2268
result.master_branch = master_branch
2300
2269
result.local_branch = self.target
2303
2272
master_branch = None
2304
2273
# no master branch
2305
result = self._basic_push(
2306
overwrite, stop_revision, tag_selector=tag_selector)
2274
result = self._basic_push(overwrite, stop_revision)
2307
2275
# TODO: Why set master_branch and local_branch if there's no
2308
2276
# binding? Maybe cleaner to just leave them unset? -- mbp
2315
def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2283
def _basic_push(self, overwrite, stop_revision):
2316
2284
"""Basic implementation of push without bound branches or hooks.
2318
2286
Must be called with source read locked and target write locked.
2321
2289
result.source_branch = self.source
2322
2290
result.target_branch = self.target
2323
2291
result.old_revno, result.old_revid = self.target.last_revision_info()
2292
self.source.update_references(self.target)
2324
2293
overwrite = _fix_overwrite_type(overwrite)
2325
2294
if result.old_revid != stop_revision:
2326
2295
# We assume that during 'push' this repository is closer than
2328
2297
graph = self.source.repository.get_graph(self.target.repository)
2329
self._update_revisions(
2330
stop_revision, overwrite=("history" in overwrite), graph=graph)
2298
self._update_revisions(stop_revision,
2299
overwrite=("history" in overwrite),
2331
2301
if self.source._push_should_merge_tags():
2332
2302
result.tag_updates, result.tag_conflicts = (
2333
2303
self.source.tags.merge_to(
2334
self.target.tags, "tags" in overwrite, selector=tag_selector))
2335
self.update_references()
2304
self.target.tags, "tags" in overwrite))
2336
2305
result.new_revno, result.new_revid = self.target.last_revision_info()
2339
2308
def _pull(self, overwrite=False, stop_revision=None,
2340
possible_transports=None, _hook_master=None, run_hooks=True,
2341
_override_hook_target=None, local=False,
2342
merge_tags_to_master=True, tag_selector=None):
2309
possible_transports=None, _hook_master=None, run_hooks=True,
2310
_override_hook_target=None, local=False,
2311
merge_tags_to_master=True):
2343
2312
"""See Branch.pull.
2345
2314
This function is the core worker, used by GenericInterBranch.pull to
2368
2337
with self.source.lock_read():
2369
2338
# We assume that during 'pull' the target repository is closer than
2370
2339
# the source one.
2340
self.source.update_references(self.target)
2371
2341
graph = self.target.repository.get_graph(self.source.repository)
2372
# TODO: Branch formats should have a flag that indicates
2342
# TODO: Branch formats should have a flag that indicates
2373
2343
# that revno's are expensive, and pull() should honor that flag.
2374
2344
# -- JRV20090506
2375
2345
result.old_revno, result.old_revid = \
2376
2346
self.target.last_revision_info()
2377
2347
overwrite = _fix_overwrite_type(overwrite)
2378
self._update_revisions(
2379
stop_revision, overwrite=("history" in overwrite), graph=graph)
2380
# TODO: The old revid should be specified when merging tags,
2381
# so a tags implementation that versions tags can only
2348
self._update_revisions(stop_revision,
2349
overwrite=("history" in overwrite),
2351
# TODO: The old revid should be specified when merging tags,
2352
# so a tags implementation that versions tags can only
2382
2353
# pull in the most recent changes. -- JRV20090506
2383
2354
result.tag_updates, result.tag_conflicts = (
2384
self.source.tags.merge_to(
2385
self.target.tags, "tags" in overwrite,
2386
ignore_master=not merge_tags_to_master,
2387
selector=tag_selector))
2388
self.update_references()
2389
result.new_revno, result.new_revid = (
2390
self.target.last_revision_info())
2355
self.source.tags.merge_to(self.target.tags,
2356
"tags" in overwrite,
2357
ignore_master=not merge_tags_to_master))
2358
result.new_revno, result.new_revid = self.target.last_revision_info()
2391
2359
if _hook_master:
2392
2360
result.master_branch = _hook_master
2393
2361
result.local_branch = result.target_branch
2402
def update_references(self):
2403
if not getattr(self.source._format, 'supports_reference_locations', False):
2405
reference_dict = self.source._get_all_reference_info()
2406
if len(reference_dict) == 0:
2408
old_base = self.source.base
2409
new_base = self.target.base
2410
target_reference_dict = self.target._get_all_reference_info()
2411
for tree_path, (branch_location, file_id) in viewitems(reference_dict):
2413
branch_location = urlutils.rebase_url(branch_location,
2415
except urlutils.InvalidRebaseURLs:
2416
# Fall back to absolute URL
2417
branch_location = urlutils.join(old_base, branch_location)
2418
target_reference_dict.setdefault(
2419
tree_path, (branch_location, file_id))
2420
self.target._set_all_reference_info(target_reference_dict)
2423
2371
InterBranch.register_optimiser(GenericInterBranch)