48
from .decorators import (
47
51
from .hooks import Hooks
48
52
from .inter import InterObject
49
53
from .lock import LogicalLockResult
50
54
from .sixish import (
54
from .trace import mutter, mutter_callsite, note, is_quiet, warning
59
from .trace import mutter, mutter_callsite, note, is_quiet
57
62
class UnstackableBranchFormat(errors.BzrError):
59
64
_fmt = ("The branch '%(url)s'(%(format)s) is not a stackable format. "
60
"You will need to upgrade the branch to permit branch stacking.")
65
"You will need to upgrade the branch to permit branch stacking.")
62
67
def __init__(self, format, url):
63
68
errors.BzrError.__init__(self)
174
178
For instance, if the branch is at URL/.bzr/branch,
175
179
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,
181
control = controldir.ControlDir.open(base,
182
possible_transports=possible_transports, _unsupported=_unsupported)
183
return control.open_branch(unsupported=_unsupported,
182
184
possible_transports=possible_transports)
185
187
def open_from_transport(transport, name=None, _unsupported=False,
186
possible_transports=None):
188
possible_transports=None):
187
189
"""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,
190
control = controldir.ControlDir.open_from_transport(transport, _unsupported)
191
return control.open_branch(name=name, unsupported=_unsupported,
192
192
possible_transports=possible_transports)
200
200
Basically we keep looking up until we find the control directory or
201
201
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
202
If there is one and it is either an unrecognised format or an unsupported
203
format, UnknownFormatError or UnsupportedFormatError are raised.
204
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)
206
control, relpath = controldir.ControlDir.open_containing(url,
209
208
branch = control.open_branch(possible_transports=possible_transports)
210
209
return (branch, relpath)
416
412
:return: A dictionary mapping revision_id => dotted revno.
417
413
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
415
with self.lock_read():
423
416
if self._revision_id_to_revno_cache is not None:
424
417
mapping = self._revision_id_to_revno_cache
426
419
mapping = self._gen_revno_map()
427
420
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.
421
# TODO: jam 20070417 Since this is being cached, should we be returning
423
# I would rather not, and instead just declare that users should not
424
# modify the return value.
434
427
def _gen_revno_map(self):
442
435
: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()}
437
revision_id_to_revno = dict((rev_id, revno)
438
for rev_id, depth, revno, end_of_merge
439
in self.iter_merge_sorted_revisions())
447
440
return revision_id_to_revno
449
442
def iter_merge_sorted_revisions(self, start_revision_id=None,
450
stop_revision_id=None,
451
stop_rule='exclude', direction='reverse'):
443
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
452
444
"""Walk the revisions for a branch in merge sorted order.
454
446
Merge sorted order is the output from a merge-aware,
577
568
if rev_id == left_parent:
578
569
# reached the left parent after the stop_revision
580
if (not reached_stop_revision_id
581
or rev_id in revision_id_whitelist):
571
if (not reached_stop_revision_id or
572
rev_id in revision_id_whitelist):
582
573
yield (rev_id, node.merge_depth, node.revno,
584
575
if reached_stop_revision_id or rev_id == stop_revision_id:
585
576
# only do the merged revs of rev_id from now on
586
577
rev = self.repository.get_revision(rev_id)
676
662
raise errors.UpgradeRequired(self.user_url)
677
663
self.get_config_stack().set('append_revisions_only', enabled)
679
def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
665
def set_reference_info(self, tree_path, branch_location, file_id=None):
666
"""Set the branch location to use for a tree reference."""
667
raise errors.UnsupportedOperation(self.set_reference_info, self)
669
def get_reference_info(self, path):
670
"""Get the tree_path and branch_location for a tree reference."""
671
raise errors.UnsupportedOperation(self.get_reference_info, self)
673
def fetch(self, from_branch, last_revision=None, limit=None):
680
674
"""Copy revisions from from_branch into this branch.
682
676
:param from_branch: Where to copy from.
683
:param stop_revision: What revision to stop at (None for at the end
677
:param last_revision: What revision to stop at (None for at the end
685
679
:param limit: Optional rough limit of revisions to fetch
688
682
with self.lock_write():
689
683
return InterBranch.get(from_branch, self).fetch(
690
stop_revision, limit=limit, lossy=lossy)
684
last_revision, limit=limit)
692
686
def get_bound_location(self):
693
687
"""Return the URL of the branch we are bound to.
715
709
:param revprops: Optional dictionary of revision properties.
716
710
:param revision_id: Optional revision id.
717
711
:param lossy: Whether to discard data that can not be natively
718
represented, when pushing to a foreign VCS
712
represented, when pushing to a foreign VCS
721
715
if config_stack is None:
722
716
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)
718
return self.repository.get_commit_builder(self, parents, config_stack,
719
timestamp, timezone, committer, revprops, revision_id,
728
722
def get_master_branch(self, possible_transports=None):
729
723
"""Return the branch we are bound to.
768
762
if not graph.is_ancestor(last_rev, revision_id):
769
763
# our previous tip is not merged into stop_revision
770
764
raise errors.DivergedBranches(self, other_branch)
771
revno = graph.find_distance_to_null(
772
revision_id, known_revision_ids)
765
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
773
766
self.set_last_revision_info(revno, revision_id)
775
768
def set_parent(self, url):
781
774
if url is not None:
782
775
if isinstance(url, text_type):
777
url = url.encode('ascii')
785
778
except UnicodeEncodeError:
786
raise urlutils.InvalidURL(
787
url, "Urls must be 7-bit ascii, "
779
raise urlutils.InvalidURL(url,
780
"Urls must be 7-bit ascii, "
788
781
"use breezy.urlutils.escape")
789
782
url = urlutils.relative_url(self.base, url)
790
783
with self.lock_write():
801
794
if not self._format.supports_stacking():
802
795
raise UnstackableBranchFormat(self._format, self.user_url)
803
796
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.
797
# XXX: Changing from one fallback repository to another does not check
798
# that all the data you need is present in the new fallback.
806
799
# Possibly it should.
807
800
self._check_stackable_repo()
810
self.get_stacked_on_url()
803
old_url = self.get_stacked_on_url()
811
804
except (errors.NotStacked, UnstackableBranchFormat,
812
errors.UnstackableRepositoryFormat):
805
errors.UnstackableRepositoryFormat):
816
self._activate_fallback_location(
817
url, possible_transports=[self.controldir.root_transport])
809
self._activate_fallback_location(url,
810
possible_transports=[self.controldir.root_transport])
818
811
# write this out after the repository is stacked to avoid setting a
819
812
# stacked config that doesn't work.
820
813
self._set_config_location('stacked_on_location', url)
828
821
pb.update(gettext("Unstacking"))
829
822
# The basic approach here is to fetch the tip of the branch,
830
823
# including all available ghosts, from the existing stacked
831
# repository into a new repository object without the fallbacks.
824
# repository into a new repository object without the fallbacks.
833
826
# XXX: See <https://launchpad.net/bugs/397286> - this may not be
834
827
# correct for CHKMap repostiories
835
828
old_repository = self.repository
836
829
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))
830
raise AssertionError("can't cope with fallback repositories "
831
"of %r (fallbacks: %r)" % (old_repository,
832
old_repository._fallback_repositories))
841
833
# Open the new repository object.
842
834
# Repositories don't offer an interface to remove fallback
843
835
# repositories today; take the conceptually simpler option and just
895
887
tags_to_fetch = set(self.tags.get_reverse_tag_dict())
896
888
except errors.TagsNotSupported:
897
889
tags_to_fetch = set()
898
fetch_spec = vf_search.NotInOtherForRevs(
899
self.repository, old_repository,
900
required_ids=[self.last_revision()],
890
fetch_spec = vf_search.NotInOtherForRevs(self.repository,
891
old_repository, required_ids=[self.last_revision()],
901
892
if_present_ids=tags_to_fetch, find_ghosts=True).execute()
902
893
self.repository.fetch(old_repository, fetch_spec=fetch_spec)
1047
1037
:returns: PullResult instance
1049
return InterBranch.get(source, self).pull(
1050
overwrite=overwrite, stop_revision=stop_revision,
1039
return InterBranch.get(source, self).pull(overwrite=overwrite,
1040
stop_revision=stop_revision,
1051
1041
possible_transports=possible_transports, *args, **kwargs)
1053
1043
def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1055
1045
"""Mirror this branch into target.
1057
1047
This branch is considered to be 'local', having low latency.
1059
return InterBranch.get(self, target).push(
1060
overwrite, stop_revision, lossy, *args, **kwargs)
1049
return InterBranch.get(self, target).push(overwrite, stop_revision,
1050
lossy, *args, **kwargs)
1062
1052
def basis_tree(self):
1063
1053
"""Return `Tree` object for last revision."""
1194
1184
if revno < 1 or revno > self.revno():
1195
1185
raise errors.InvalidRevisionNumber(revno)
1197
def clone(self, to_controldir, revision_id=None, name=None,
1198
repository_policy=None, tag_selector=None):
1187
def clone(self, to_controldir, revision_id=None, repository_policy=None):
1199
1188
"""Clone this branch into to_controldir preserving all semantic values.
1201
1190
Most API users will want 'create_clone_on_transport', which creates a
1204
1193
revision_id: if not None, the revision history in the new branch will
1205
1194
be truncated to end with revision_id.
1207
result = to_controldir.create_branch(name=name)
1196
result = to_controldir.create_branch()
1208
1197
with self.lock_read(), result.lock_write():
1209
1198
if repository_policy is not None:
1210
1199
repository_policy.configure_branch(result)
1211
self.copy_content_into(
1212
result, revision_id=revision_id, tag_selector=tag_selector)
1200
self.copy_content_into(result, revision_id=revision_id)
1215
1203
def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1216
repository=None, lossy=False, tag_selector=None):
1204
repository=None, lossy=False):
1217
1205
"""Create a new line of development from the branch, into to_controldir.
1219
1207
to_controldir controls the branch format.
1258
1245
graph = self.repository.get_graph()
1260
revno = graph.find_distance_to_null(
1261
revision_id, [(source_revision_id, source_revno)])
1247
revno = graph.find_distance_to_null(revision_id,
1248
[(source_revision_id, source_revno)])
1262
1249
except errors.GhostRevisionsHaveNoRevno:
1263
1250
# Default to 1, if we can't find anything else
1265
1252
destination.set_last_revision_info(revno, revision_id)
1267
def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1254
def copy_content_into(self, destination, revision_id=None):
1268
1255
"""Copy the content of self into destination.
1270
1257
revision_id: if not None, the revision history in the new branch will
1271
1258
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
1260
return InterBranch.get(self, destination).copy_content_into(
1276
revision_id=revision_id, tag_selector=tag_selector)
1261
revision_id=revision_id)
1278
1263
def update_references(self, target):
1279
if not self._format.supports_reference_locations:
1281
return InterBranch.get(self, target).update_references()
1264
if not getattr(self._format, 'supports_reference_locations', False):
1266
reference_dict = self._get_all_reference_info()
1267
if len(reference_dict) == 0:
1269
old_base = self.base
1270
new_base = target.base
1271
target_reference_dict = target._get_all_reference_info()
1272
for tree_path, (branch_location, file_id) in viewitems(reference_dict):
1273
branch_location = urlutils.rebase_url(branch_location,
1275
target_reference_dict.setdefault(
1276
tree_path, (branch_location, file_id))
1277
target._set_all_reference_info(target_reference_dict)
1283
1279
def check(self, refs):
1284
1280
"""Check consistency of the branch.
1300
1296
if actual_revno != last_revno:
1301
1297
result.errors.append(errors.BzrCheckError(
1302
1298
'revno does not match len(mainline) %s != %s' % (
1303
last_revno, actual_revno)))
1299
last_revno, actual_revno)))
1304
1300
# TODO: We should probably also check that self.revision_history
1305
1301
# 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.
1302
# If looking for the code that cross-checks repository parents against
1303
# the Graph.iter_lefthand_ancestry output, that is now a repository
1311
1307
def _get_checkout_format(self, lightweight=False):
1333
1328
# XXX: Fix the bzrdir API to allow getting the branch back from the
1334
1329
# clone call. Or something. 20090224 RBC/spiv.
1335
# XXX: Should this perhaps clone colocated branches as well,
1330
# XXX: Should this perhaps clone colocated branches as well,
1336
1331
# rather than just the default branch? 20100319 JRV
1337
1332
if revision_id is None:
1338
1333
revision_id = self.last_revision()
1339
dir_to = self.controldir.clone_on_transport(
1340
to_transport, revision_id=revision_id, stacked_on=stacked_on,
1334
dir_to = self.controldir.clone_on_transport(to_transport,
1335
revision_id=revision_id, stacked_on=stacked_on,
1341
1336
create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1342
no_tree=no_tree, tag_selector=tag_selector)
1343
1338
return dir_to.open_branch()
1345
1340
def create_checkout(self, to_location, revision_id=None,
1346
1341
lightweight=False, accelerator_tree=None,
1347
hardlink=False, recurse_nested=True):
1348
1343
"""Create a checkout of a branch.
1350
1345
:param to_location: The url to produce the checkout at
1398
1391
hardlink=hardlink)
1399
1392
basis_tree = tree.basis_tree()
1400
1393
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)
1394
for path, file_id in basis_tree.iter_references():
1395
reference_parent = self.reference_parent(path, file_id)
1396
reference_parent.create_checkout(tree.abspath(path),
1397
basis_tree.get_reference_revision(path, file_id),
1411
1401
def reconcile(self, thorough=True):
1412
"""Make sure the data stored in this branch is consistent.
1414
:return: A `ReconcileResult` object.
1402
"""Make sure the data stored in this branch is consistent."""
1403
from breezy.reconcile import BranchReconciler
1404
with self.lock_write():
1405
reconciler = BranchReconciler(self, thorough=thorough)
1406
reconciler.reconcile()
1409
def reference_parent(self, path, file_id=None, possible_transports=None):
1410
"""Return the parent branch for a tree-reference file_id
1412
:param path: The path of the file_id in the tree
1413
:param file_id: Optional file_id of the tree reference
1414
:return: A branch associated with the file_id
1416
raise NotImplementedError(self.reconcile)
1416
# FIXME should provide multiple branches, based on config
1417
return Branch.open(self.controldir.root_transport.clone(path).base,
1418
possible_transports=possible_transports)
1418
1420
def supports_tags(self):
1419
1421
return self._format.supports_tags()
1664
1662
Hooks.__init__(self, "breezy.branch", "Branch.hooks")
1663
self.add_hook('open',
1667
1664
"Called with the Branch object that has been opened after a "
1668
1665
"branch is opened.", (1, 8))
1666
self.add_hook('post_push',
1671
1667
"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))
1668
"with a breezy.branch.BranchPushResult object and only runs in the "
1669
"bzr client.", (0, 15))
1670
self.add_hook('post_pull',
1676
1671
"Called after a pull operation completes. post_pull is called "
1677
1672
"with a breezy.branch.PullResult object and only runs in the "
1678
1673
"bzr client.", (0, 15))
1674
self.add_hook('pre_commit',
1681
1675
"Called after a commit is calculated but before it is "
1682
1676
"completed. pre_commit is called with (local, master, old_revno, "
1683
1677
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1687
1681
" future_tree is an in-memory tree obtained from "
1688
1682
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1689
1683
"tree.", (0, 91))
1684
self.add_hook('post_commit',
1692
1685
"Called in the bzr client after a commit has completed. "
1693
1686
"post_commit is called with (local, master, old_revno, old_revid, "
1694
1687
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1695
1688
"commit to a branch.", (0, 15))
1689
self.add_hook('post_uncommit',
1698
1690
"Called in the bzr client after an uncommit completes. "
1699
1691
"post_uncommit is called with (local, master, old_revno, "
1700
1692
"old_revid, new_revno, new_revid) where local is the local branch "
1701
1693
"or None, master is the target branch, and an empty branch "
1702
1694
"receives new_revno of 0, new_revid of None.", (0, 15))
1704
'pre_change_branch_tip',
1695
self.add_hook('pre_change_branch_tip',
1705
1696
"Called in bzr client and server before a change to the tip of a "
1706
1697
"branch is made. pre_change_branch_tip is called with a "
1707
1698
"breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1708
1699
"commit, uncommit will all trigger this hook.", (1, 6))
1710
'post_change_branch_tip',
1700
self.add_hook('post_change_branch_tip',
1711
1701
"Called in bzr client and server after a change to the tip of a "
1712
1702
"branch is made. post_change_branch_tip is called with a "
1713
1703
"breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1714
1704
"commit, uncommit will all trigger this hook.", (1, 4))
1716
'transform_fallback_location',
1705
self.add_hook('transform_fallback_location',
1717
1706
"Called when a stacked branch is activating its fallback "
1718
1707
"locations. transform_fallback_location is called with (branch, "
1719
1708
"url), and should return a new url. Returning the same url "
1725
1714
"multiple hooks installed for transform_fallback_location, "
1726
1715
"all are called with the url returned from the previous hook."
1727
1716
"The order is however undefined.", (1, 9))
1729
'automatic_tag_name',
1717
self.add_hook('automatic_tag_name',
1730
1718
"Called to determine an automatic tag name for a revision. "
1731
1719
"automatic_tag_name is called with (branch, revision_id) and "
1732
1720
"should return a tag name or None if no tag name could be "
1733
1721
"determined. The first non-None tag name returned will be used.",
1723
self.add_hook('post_branch_init',
1737
1724
"Called after new branch initialization completes. "
1738
1725
"post_branch_init is called with a "
1739
1726
"breezy.branch.BranchInitHookParams. "
1740
1727
"Note that init, branch and checkout (both heavyweight and "
1741
1728
"lightweight) will all trigger this hook.", (2, 2))
1729
self.add_hook('post_switch',
1744
1730
"Called after a checkout switches branch. "
1745
1731
"post_switch is called with a "
1746
1732
"breezy.branch.SwitchHookParams.", (2, 2))
1749
1736
# install the default hooks into the Branch class.
1750
1737
Branch.hooks = BranchHooks()
2005
1992
tag_updates = getattr(self, "tag_updates", None)
2006
1993
if not is_quiet():
2007
1994
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'))
1995
note(gettext('Pushed up to revision %d.') % self.new_revno)
2014
1996
if tag_updates:
2015
note(ngettext('%d tag updated.', '%d tags updated.',
2016
len(tag_updates)) % len(tag_updates))
1997
note(ngettext('%d tag updated.', '%d tags updated.', len(tag_updates)) % len(tag_updates))
2017
1998
if self.old_revid == self.new_revid and not tag_updates:
2018
1999
if not tag_conflicts:
2019
2000
note(gettext('No new revisions or tags to push.'))
2076
2057
raise NotImplementedError(self.pull)
2078
2059
def push(self, overwrite=False, stop_revision=None, lossy=False,
2079
_override_hook_source_branch=None, tag_selector=None):
2060
_override_hook_source_branch=None):
2080
2061
"""Mirror the source branch into the target branch.
2082
2063
The source branch is considered to be 'local', having low latency.
2084
2065
raise NotImplementedError(self.push)
2086
def copy_content_into(self, revision_id=None, tag_selector=None):
2067
def copy_content_into(self, revision_id=None):
2087
2068
"""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.
2070
revision_id: if not None, the revision history in the new branch will
2071
be truncated to end with revision_id.
2095
2073
raise NotImplementedError(self.copy_content_into)
2097
def fetch(self, stop_revision=None, limit=None, lossy=False):
2075
def fetch(self, stop_revision=None, limit=None):
2098
2076
"""Fetch revisions.
2100
2078
:param stop_revision: Last revision to fetch
2101
2079
:param limit: Optional rough limit of revisions to fetch
2102
:return: FetchResult object
2104
2081
raise NotImplementedError(self.fetch)
2106
def update_references(self):
2107
"""Import reference information from source to target.
2109
raise NotImplementedError(self.update_references)
2112
2084
def _fix_overwrite_type(overwrite):
2113
2085
if isinstance(overwrite, bool):
2137
2109
return format._custom_format
2140
def copy_content_into(self, revision_id=None, tag_selector=None):
2112
def copy_content_into(self, revision_id=None):
2141
2113
"""Copy the content of source into target
2143
2115
revision_id: if not None, the revision history in the new branch will
2144
2116
be truncated to end with revision_id.
2146
2118
with self.source.lock_read(), self.target.lock_write():
2119
self.source.update_references(self.target)
2147
2120
self.source._synchronize_history(self.target, revision_id)
2148
self.update_references()
2150
2122
parent = self.source.get_parent()
2151
2123
except errors.InaccessibleParent as e:
2152
mutter('parent was not accessible to copy: %s', str(e))
2124
mutter('parent was not accessible to copy: %s', e)
2155
2127
self.target.set_parent(parent)
2156
2128
if self.source._push_should_merge_tags():
2157
self.source.tags.merge_to(self.target.tags, selector=tag_selector)
2129
self.source.tags.merge_to(self.target.tags)
2159
def fetch(self, stop_revision=None, limit=None, lossy=False):
2131
def fetch(self, stop_revision=None, limit=None):
2160
2132
if self.target.base == self.source.base:
2162
2134
with self.source.lock_read(), self.target.lock_write():
2165
2137
fetch_spec_factory.source_branch_stop_revision_id = stop_revision
2166
2138
fetch_spec_factory.source_repo = self.source.repository
2167
2139
fetch_spec_factory.target_repo = self.target.repository
2168
fetch_spec_factory.target_repo_kind = (
2169
fetch.TargetRepoKinds.PREEXISTING)
2140
fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
2170
2141
fetch_spec_factory.limit = limit
2171
2142
fetch_spec = fetch_spec_factory.make_fetch_spec()
2172
2143
return self.target.repository.fetch(
2173
self.source.repository,
2175
fetch_spec=fetch_spec)
2144
self.source.repository,
2145
fetch_spec=fetch_spec)
2177
2147
def _update_revisions(self, stop_revision=None, overwrite=False,
2179
2149
with self.source.lock_read(), self.target.lock_write():
2180
2150
other_revno, other_last_revision = self.source.last_revision_info()
2181
stop_revno = None # unknown
2151
stop_revno = None # unknown
2182
2152
if stop_revision is None:
2183
2153
stop_revision = other_last_revision
2184
2154
if _mod_revision.is_null(stop_revision):
2207
2177
if graph is None:
2208
2178
graph = self.target.repository.get_graph()
2209
2179
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)])
2180
self.target.last_revision_info()
2181
stop_revno = graph.find_distance_to_null(stop_revision,
2182
[(other_last_revision, other_revno),
2183
(this_last_revision, this_revno)])
2214
2184
self.target.set_last_revision_info(stop_revno, stop_revision)
2216
2186
def pull(self, overwrite=False, stop_revision=None,
2217
2187
possible_transports=None, run_hooks=True,
2218
_override_hook_target=None, local=False,
2188
_override_hook_target=None, local=False):
2220
2189
"""Pull from source into self, updating my master if any.
2222
2191
:param run_hooks: Private parameter - if false, this branch
2223
2192
is being called because it's the master of the primary branch,
2224
2193
so it should not run its hooks.
2226
with cleanup.ExitStack() as exit_stack:
2227
exit_stack.enter_context(self.target.lock_write())
2195
with self.target.lock_write():
2228
2196
bound_location = self.target.get_bound_location()
2229
2197
if local and not bound_location:
2230
2198
raise errors.LocalRequiresBoundBranch()
2241
2209
source_is_master = False
2242
2210
if not local and bound_location and not source_is_master:
2243
2211
# 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)
2212
master_branch = self.target.get_master_branch(possible_transports)
2213
master_branch.lock_write()
2216
# pull from source into master.
2217
master_branch.pull(self.source, overwrite, stop_revision,
2219
return self._pull(overwrite,
2220
stop_revision, _hook_master=master_branch,
2221
run_hooks=run_hooks,
2222
_override_hook_target=_override_hook_target,
2223
merge_tags_to_master=not source_is_master)
2226
master_branch.unlock()
2259
2228
def push(self, overwrite=False, stop_revision=None, lossy=False,
2260
_override_hook_source_branch=None, tag_selector=None):
2229
_override_hook_source_branch=None):
2261
2230
"""See InterBranch.push.
2263
2232
This is the basic concrete implementation of push()
2272
2241
# TODO: Public option to disable running hooks - should be trivial but
2276
if _override_hook_source_branch:
2277
result.source_branch = _override_hook_source_branch
2278
for hook in Branch.hooks['post_push']:
2281
with self.source.lock_read(), self.target.lock_write():
2282
bound_location = self.target.get_bound_location()
2283
if bound_location and self.target.base != bound_location:
2284
# 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
2288
master_branch = self.target.get_master_branch()
2289
with master_branch.lock_write():
2290
# push into the master from the source branch.
2291
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)
2299
result.master_branch = master_branch
2300
result.local_branch = self.target
2303
master_branch = None
2305
result = self._basic_push(
2306
overwrite, stop_revision, tag_selector=tag_selector)
2307
# TODO: Why set master_branch and local_branch if there's no
2308
# binding? Maybe cleaner to just leave them unset? -- mbp
2310
result.master_branch = self.target
2311
result.local_branch = None
2315
def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2244
op = cleanup.OperationWithCleanups(self._push_with_bound_branches)
2245
op.add_cleanup(self.source.lock_read().unlock)
2246
op.add_cleanup(self.target.lock_write().unlock)
2247
return op.run(overwrite, stop_revision,
2248
_override_hook_source_branch=_override_hook_source_branch)
2250
def _basic_push(self, overwrite, stop_revision):
2316
2251
"""Basic implementation of push without bound branches or hooks.
2318
2253
Must be called with source read locked and target write locked.
2321
2256
result.source_branch = self.source
2322
2257
result.target_branch = self.target
2323
2258
result.old_revno, result.old_revid = self.target.last_revision_info()
2259
self.source.update_references(self.target)
2324
2260
overwrite = _fix_overwrite_type(overwrite)
2325
2261
if result.old_revid != stop_revision:
2326
2262
# We assume that during 'push' this repository is closer than
2328
2264
graph = self.source.repository.get_graph(self.target.repository)
2329
self._update_revisions(
2330
stop_revision, overwrite=("history" in overwrite), graph=graph)
2265
self._update_revisions(stop_revision,
2266
overwrite=("history" in overwrite),
2331
2268
if self.source._push_should_merge_tags():
2332
2269
result.tag_updates, result.tag_conflicts = (
2333
2270
self.source.tags.merge_to(
2334
self.target.tags, "tags" in overwrite, selector=tag_selector))
2335
self.update_references()
2271
self.target.tags, "tags" in overwrite))
2336
2272
result.new_revno, result.new_revid = self.target.last_revision_info()
2275
def _push_with_bound_branches(self, operation, overwrite, stop_revision,
2276
_override_hook_source_branch=None):
2277
"""Push from source into target, and into target's master if any.
2280
if _override_hook_source_branch:
2281
result.source_branch = _override_hook_source_branch
2282
for hook in Branch.hooks['post_push']:
2285
bound_location = self.target.get_bound_location()
2286
if bound_location and self.target.base != bound_location:
2287
# there is a master branch.
2289
# XXX: Why the second check? Is it even supported for a branch to
2290
# be bound to itself? -- mbp 20070507
2291
master_branch = self.target.get_master_branch()
2292
master_branch.lock_write()
2293
operation.add_cleanup(master_branch.unlock)
2294
# push into the master from the source branch.
2295
master_inter = InterBranch.get(self.source, master_branch)
2296
master_inter._basic_push(overwrite, stop_revision)
2297
# and push into the target branch from the source. Note that
2298
# we push from the source branch again, because it's considered
2299
# the highest bandwidth repository.
2300
result = self._basic_push(overwrite, stop_revision)
2301
result.master_branch = master_branch
2302
result.local_branch = self.target
2304
master_branch = None
2306
result = self._basic_push(overwrite, stop_revision)
2307
# TODO: Why set master_branch and local_branch if there's no
2308
# binding? Maybe cleaner to just leave them unset? -- mbp
2310
result.master_branch = self.target
2311
result.local_branch = None
2339
2315
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):
2316
possible_transports=None, _hook_master=None, run_hooks=True,
2317
_override_hook_target=None, local=False,
2318
merge_tags_to_master=True):
2343
2319
"""See Branch.pull.
2345
2321
This function is the core worker, used by GenericInterBranch.pull to
2368
2344
with self.source.lock_read():
2369
2345
# We assume that during 'pull' the target repository is closer than
2370
2346
# the source one.
2347
self.source.update_references(self.target)
2371
2348
graph = self.target.repository.get_graph(self.source.repository)
2372
# TODO: Branch formats should have a flag that indicates
2349
# TODO: Branch formats should have a flag that indicates
2373
2350
# that revno's are expensive, and pull() should honor that flag.
2374
2351
# -- JRV20090506
2375
2352
result.old_revno, result.old_revid = \
2376
2353
self.target.last_revision_info()
2377
2354
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
2355
self._update_revisions(stop_revision,
2356
overwrite=("history" in overwrite),
2358
# TODO: The old revid should be specified when merging tags,
2359
# so a tags implementation that versions tags can only
2382
2360
# pull in the most recent changes. -- JRV20090506
2383
2361
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())
2362
self.source.tags.merge_to(self.target.tags,
2363
"tags" in overwrite,
2364
ignore_master=not merge_tags_to_master))
2365
result.new_revno, result.new_revid = self.target.last_revision_info()
2391
2366
if _hook_master:
2392
2367
result.master_branch = _hook_master
2393
2368
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
2378
InterBranch.register_optimiser(GenericInterBranch)