14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
17
21
from .lazy_import import lazy_import
18
22
lazy_import(globals(), """
21
24
from breezy import (
22
26
config as _mod_config,
26
31
revision as _mod_revision,
48
from .decorators import (
45
51
from .hooks import Hooks
46
52
from .inter import InterObject
47
53
from .lock import LogicalLockResult
48
from .trace import mutter, mutter_callsite, note, is_quiet, warning
59
from .trace import mutter, mutter_callsite, note, is_quiet
51
62
class UnstackableBranchFormat(errors.BzrError):
53
64
_fmt = ("The branch '%(url)s'(%(format)s) is not a stackable format. "
54
"You will need to upgrade the branch to permit branch stacking.")
65
"You will need to upgrade the branch to permit branch stacking.")
56
67
def __init__(self, format, url):
57
68
errors.BzrError.__init__(self)
149
160
repository._iter_for_revno(
150
161
self.repository, self._partial_revision_history_cache,
151
162
stop_index=stop_index, stop_revision=stop_revision)
152
if self._partial_revision_history_cache[-1] == \
153
_mod_revision.NULL_REVISION:
163
if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
154
164
self._partial_revision_history_cache.pop()
156
166
def _get_check_refs(self):
168
178
For instance, if the branch is at URL/.bzr/branch,
169
179
Branch.open(URL) -> a Branch instance.
171
control = controldir.ControlDir.open(
172
base, possible_transports=possible_transports,
173
_unsupported=_unsupported)
174
return control.open_branch(
175
unsupported=_unsupported,
181
control = controldir.ControlDir.open(base,
182
possible_transports=possible_transports, _unsupported=_unsupported)
183
return control.open_branch(unsupported=_unsupported,
176
184
possible_transports=possible_transports)
179
187
def open_from_transport(transport, name=None, _unsupported=False,
180
possible_transports=None):
188
possible_transports=None):
181
189
"""Open the branch rooted at transport"""
182
control = controldir.ControlDir.open_from_transport(
183
transport, _unsupported)
184
return control.open_branch(
185
name=name, unsupported=_unsupported,
190
control = controldir.ControlDir.open_from_transport(transport, _unsupported)
191
return control.open_branch(name=name, unsupported=_unsupported,
186
192
possible_transports=possible_transports)
194
200
Basically we keep looking up until we find the control directory or
195
201
run into the root. If there isn't one, raises NotBranchError.
196
If there is one and it is either an unrecognised format or an
197
unsupported format, UnknownFormatError or UnsupportedFormatError are
198
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.
201
control, relpath = controldir.ControlDir.open_containing(
202
url, possible_transports)
206
control, relpath = controldir.ControlDir.open_containing(url,
203
208
branch = control.open_branch(possible_transports=possible_transports)
204
209
return (branch, relpath)
271
276
# Silently fall back to local implicit nick if the master is
273
278
mutter("Could not connect to bound branch, "
274
"falling back to local nick.\n " + str(e))
279
"falling back to local nick.\n " + str(e))
275
280
return config.get_nickname()
277
282
def _set_nick(self, nick):
301
306
check_not_reserved_id = _mod_revision.check_not_reserved_id
302
307
# Do not include ghosts or graph origin in revision_history
303
while (current_rev_id in parents_map
304
and len(parents_map[current_rev_id]) > 0):
308
while (current_rev_id in parents_map and
309
len(parents_map[current_rev_id]) > 0):
305
310
check_not_reserved_id(current_rev_id)
306
311
new_history.append(current_rev_id)
307
312
current_rev_id = parents_map[current_rev_id][0]
359
364
provide a more efficient implementation.
361
366
if len(revno) == 1:
363
return self.get_rev_id(revno[0])
364
except errors.RevisionNotPresent as e:
365
raise errors.GhostRevisionsHaveNoRevno(revno[0], e.revision_id)
367
return self.get_rev_id(revno[0])
366
368
revision_id_to_revno = self.get_revision_id_to_revno_map()
367
369
revision_ids = [revision_id for revision_id, this_revno
368
in revision_id_to_revno.items()
370
in viewitems(revision_id_to_revno)
369
371
if revno == this_revno]
370
372
if len(revision_ids) == 1:
371
373
return revision_ids[0]
410
412
:return: A dictionary mapping revision_id => dotted revno.
411
413
This dictionary should not be modified by the caller.
413
if 'evil' in debug.debug_flags:
415
3, "get_revision_id_to_revno_map scales with ancestry.")
416
415
with self.lock_read():
417
416
if self._revision_id_to_revno_cache is not None:
418
417
mapping = self._revision_id_to_revno_cache
420
419
mapping = self._gen_revno_map()
421
420
self._cache_revision_id_to_revno(mapping)
422
# TODO: jam 20070417 Since this is being cached, should we be
424
# I would rather not, and instead just declare that users should
425
# 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.
428
427
def _gen_revno_map(self):
436
435
:return: A dictionary mapping revision_id => dotted revno.
438
revision_id_to_revno = {
439
rev_id: revno for rev_id, depth, revno, end_of_merge
440
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())
441
440
return revision_id_to_revno
443
442
def iter_merge_sorted_revisions(self, start_revision_id=None,
444
stop_revision_id=None,
445
stop_rule='exclude', direction='reverse'):
443
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
446
444
"""Walk the revisions for a branch in merge sorted order.
448
446
Merge sorted order is the output from a merge-aware,
460
458
* 'include' - the stop revision is the last item in the result
461
459
* 'with-merges' - include the stop revision and all of its
462
460
merged revisions in the result
463
* 'with-merges-without-common-ancestry' - filter out revisions
461
* 'with-merges-without-common-ancestry' - filter out revisions
464
462
that are in both ancestries
465
463
:param direction: either 'reverse' or 'forward':
509
507
raise ValueError('invalid direction %r' % direction)
511
509
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
512
start_revision_id, stop_revision_id,
510
start_revision_id, stop_revision_id, stop_rule):
514
511
"""Iterate over an inclusive range of sorted revisions."""
515
512
rev_iter = iter(merge_sorted_revisions)
516
513
if start_revision_id is not None:
571
568
if rev_id == left_parent:
572
569
# reached the left parent after the stop_revision
574
if (not reached_stop_revision_id
575
or rev_id in revision_id_whitelist):
571
if (not reached_stop_revision_id or
572
rev_id in revision_id_whitelist):
576
573
yield (rev_id, node.merge_depth, node.revno,
578
575
if reached_stop_revision_id or rev_id == stop_revision_id:
579
576
# only do the merged revs of rev_id from now on
580
577
rev = self.repository.get_revision(rev_id)
645
638
"""Tell this branch object to release the physical lock when this
646
639
object is unlocked, even if it didn't originally acquire it.
648
If lock_write doesn't return a token, then this method is not
641
If lock_write doesn't return a token, then this method is not supported.
651
643
self.control_files.dont_leave_in_place()
670
662
raise errors.UpgradeRequired(self.user_url)
671
663
self.get_config_stack().set('append_revisions_only', enabled)
673
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):
674
674
"""Copy revisions from from_branch into this branch.
676
676
:param from_branch: Where to copy from.
677
: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
679
679
:param limit: Optional rough limit of revisions to fetch
682
682
with self.lock_write():
683
683
return InterBranch.get(from_branch, self).fetch(
684
stop_revision, limit=limit, lossy=lossy)
684
last_revision, limit=limit)
686
686
def get_bound_location(self):
687
687
"""Return the URL of the branch we are bound to.
709
709
:param revprops: Optional dictionary of revision properties.
710
710
:param revision_id: Optional revision id.
711
711
:param lossy: Whether to discard data that can not be natively
712
represented, when pushing to a foreign VCS
712
represented, when pushing to a foreign VCS
715
715
if config_stack is None:
716
716
config_stack = self.get_config_stack()
718
return self.repository.get_commit_builder(
719
self, parents, config_stack, timestamp, timezone, committer,
720
revprops, revision_id, lossy)
718
return self.repository.get_commit_builder(self, parents, config_stack,
719
timestamp, timezone, committer, revprops, revision_id,
722
722
def get_master_branch(self, possible_transports=None):
723
723
"""Return the branch we are bound to.
762
762
if not graph.is_ancestor(last_rev, revision_id):
763
763
# our previous tip is not merged into stop_revision
764
764
raise errors.DivergedBranches(self, other_branch)
765
revno = graph.find_distance_to_null(
766
revision_id, known_revision_ids)
765
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
767
766
self.set_last_revision_info(revno, revision_id)
769
768
def set_parent(self, url):
773
772
# FIXUP this and get_parent in a future branch format bump:
774
773
# read and rewrite the file. RBC 20060125
775
774
if url is not None:
776
if isinstance(url, str):
775
if isinstance(url, text_type):
777
url = url.encode('ascii')
779
778
except UnicodeEncodeError:
780
raise urlutils.InvalidURL(
781
url, "Urls must be 7-bit ascii, "
779
raise urlutils.InvalidURL(url,
780
"Urls must be 7-bit ascii, "
782
781
"use breezy.urlutils.escape")
783
782
url = urlutils.relative_url(self.base, url)
784
783
with self.lock_write():
795
794
if not self._format.supports_stacking():
796
795
raise UnstackableBranchFormat(self._format, self.user_url)
797
796
with self.lock_write():
798
# XXX: Changing from one fallback repository to another does not
799
# 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.
800
799
# Possibly it should.
801
800
self._check_stackable_repo()
804
self.get_stacked_on_url()
803
old_url = self.get_stacked_on_url()
805
804
except (errors.NotStacked, UnstackableBranchFormat,
806
errors.UnstackableRepositoryFormat):
805
errors.UnstackableRepositoryFormat):
810
self._activate_fallback_location(
811
url, possible_transports=[self.controldir.root_transport])
809
self._activate_fallback_location(url,
810
possible_transports=[self.controldir.root_transport])
812
811
# write this out after the repository is stacked to avoid setting a
813
812
# stacked config that doesn't work.
814
813
self._set_config_location('stacked_on_location', url)
822
821
pb.update(gettext("Unstacking"))
823
822
# The basic approach here is to fetch the tip of the branch,
824
823
# including all available ghosts, from the existing stacked
825
# repository into a new repository object without the fallbacks.
824
# repository into a new repository object without the fallbacks.
827
826
# XXX: See <https://launchpad.net/bugs/397286> - this may not be
828
827
# correct for CHKMap repostiories
829
828
old_repository = self.repository
830
829
if len(old_repository._fallback_repositories) != 1:
831
raise AssertionError(
832
"can't cope with fallback repositories "
833
"of %r (fallbacks: %r)" % (
834
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))
835
833
# Open the new repository object.
836
834
# Repositories don't offer an interface to remove fallback
837
835
# repositories today; take the conceptually simpler option and just
844
842
self.controldir.root_transport.base)
845
843
new_repository = new_bzrdir.find_repository()
846
844
if new_repository._fallback_repositories:
847
raise AssertionError(
848
"didn't expect %r to have fallback_repositories"
845
raise AssertionError("didn't expect %r to have "
846
"fallback_repositories"
849
847
% (self.repository,))
850
848
# Replace self.repository with the new repository.
851
849
# Do our best to transfer the lock state (i.e. lock-tokens and
878
876
if old_lock_count == 0:
879
877
raise AssertionError(
880
878
'old_repository should have been locked at least once.')
881
for i in range(old_lock_count - 1):
879
for i in range(old_lock_count-1):
882
880
self.repository.lock_write()
883
881
# Fetch from the old repository into the new.
884
882
with old_repository.lock_read():
889
887
tags_to_fetch = set(self.tags.get_reverse_tag_dict())
890
888
except errors.TagsNotSupported:
891
889
tags_to_fetch = set()
892
fetch_spec = vf_search.NotInOtherForRevs(
893
self.repository, old_repository,
894
required_ids=[self.last_revision()],
890
fetch_spec = vf_search.NotInOtherForRevs(self.repository,
891
old_repository, required_ids=[self.last_revision()],
895
892
if_present_ids=tags_to_fetch, find_ghosts=True).execute()
896
893
self.repository.fetch(old_repository, fetch_spec=fetch_spec)
980
977
with self.lock_read():
981
978
if self._last_revision_info_cache is None:
982
self._last_revision_info_cache = (
983
self._read_last_revision_info())
979
self._last_revision_info_cache = self._read_last_revision_info()
984
980
return self._last_revision_info_cache
986
982
def _read_last_revision_info(self):
1041
1037
:returns: PullResult instance
1043
return InterBranch.get(source, self).pull(
1044
overwrite=overwrite, stop_revision=stop_revision,
1039
return InterBranch.get(source, self).pull(overwrite=overwrite,
1040
stop_revision=stop_revision,
1045
1041
possible_transports=possible_transports, *args, **kwargs)
1047
1043
def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1049
1045
"""Mirror this branch into target.
1051
1047
This branch is considered to be 'local', having low latency.
1053
return InterBranch.get(self, target).push(
1054
overwrite, stop_revision, lossy, *args, **kwargs)
1049
return InterBranch.get(self, target).push(overwrite, stop_revision,
1050
lossy, *args, **kwargs)
1056
1052
def basis_tree(self):
1057
1053
"""Return `Tree` object for last revision."""
1070
1066
# This is an old-format absolute path to a local branch
1071
1067
# turn it into a url
1072
1068
if parent.startswith('/'):
1073
parent = urlutils.local_path_to_url(parent)
1069
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1075
1071
return urlutils.join(self.base[:-1], parent)
1076
except urlutils.InvalidURLJoin:
1072
except urlutils.InvalidURLJoin as e:
1077
1073
raise errors.InaccessibleParent(parent, self.user_url)
1079
1075
def _get_parent_location(self):
1188
1184
if revno < 1 or revno > self.revno():
1189
1185
raise errors.InvalidRevisionNumber(revno)
1191
def clone(self, to_controldir, revision_id=None, name=None,
1192
repository_policy=None, tag_selector=None):
1187
def clone(self, to_controldir, revision_id=None, repository_policy=None):
1193
1188
"""Clone this branch into to_controldir preserving all semantic values.
1195
1190
Most API users will want 'create_clone_on_transport', which creates a
1198
1193
revision_id: if not None, the revision history in the new branch will
1199
1194
be truncated to end with revision_id.
1201
result = to_controldir.create_branch(name=name)
1196
result = to_controldir.create_branch()
1202
1197
with self.lock_read(), result.lock_write():
1203
1198
if repository_policy is not None:
1204
1199
repository_policy.configure_branch(result)
1205
self.copy_content_into(
1206
result, revision_id=revision_id, tag_selector=tag_selector)
1200
self.copy_content_into(result, revision_id=revision_id)
1209
1203
def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1210
repository=None, lossy=False, tag_selector=None):
1204
repository=None, lossy=False):
1211
1205
"""Create a new line of development from the branch, into to_controldir.
1213
1207
to_controldir controls the branch format.
1215
1209
revision_id: if not None, the revision history in the new branch will
1216
1210
be truncated to end with revision_id.
1218
if (repository_policy is not None
1219
and repository_policy.requires_stacking()):
1212
if (repository_policy is not None and
1213
repository_policy.requires_stacking()):
1220
1214
to_controldir._format.require_stacking(_skip_repo=True)
1221
1215
result = to_controldir.create_branch(repository=repository)
1224
1218
with self.lock_read(), result.lock_write():
1225
1219
if repository_policy is not None:
1226
1220
repository_policy.configure_branch(result)
1227
self.copy_content_into(
1228
result, revision_id=revision_id, tag_selector=tag_selector)
1221
self.copy_content_into(result, revision_id=revision_id)
1229
1222
master_url = self.get_bound_location()
1230
1223
if master_url is None:
1231
1224
result.set_parent(self.user_url)
1252
1245
graph = self.repository.get_graph()
1254
revno = graph.find_distance_to_null(
1255
revision_id, [(source_revision_id, source_revno)])
1247
revno = graph.find_distance_to_null(revision_id,
1248
[(source_revision_id, source_revno)])
1256
1249
except errors.GhostRevisionsHaveNoRevno:
1257
1250
# Default to 1, if we can't find anything else
1259
1252
destination.set_last_revision_info(revno, revision_id)
1261
def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1254
def copy_content_into(self, destination, revision_id=None):
1262
1255
"""Copy the content of self into destination.
1264
1257
revision_id: if not None, the revision history in the new branch will
1265
1258
be truncated to end with revision_id.
1266
tag_selector: Optional callback that receives a tag name
1267
and should return a boolean to indicate whether a tag should be copied
1269
1260
return InterBranch.get(self, destination).copy_content_into(
1270
revision_id=revision_id, tag_selector=tag_selector)
1261
revision_id=revision_id)
1272
1263
def update_references(self, target):
1273
if not self._format.supports_reference_locations:
1275
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)
1277
1279
def check(self, refs):
1278
1280
"""Check consistency of the branch.
1294
1296
if actual_revno != last_revno:
1295
1297
result.errors.append(errors.BzrCheckError(
1296
1298
'revno does not match len(mainline) %s != %s' % (
1297
last_revno, actual_revno)))
1299
last_revno, actual_revno)))
1298
1300
# TODO: We should probably also check that self.revision_history
1299
1301
# matches the repository for older branch formats.
1300
# If looking for the code that cross-checks repository parents
1301
# against the Graph.iter_lefthand_ancestry output, that is now a
1302
# 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
1305
1307
def _get_checkout_format(self, lightweight=False):
1313
1315
def create_clone_on_transport(self, to_transport, revision_id=None,
1314
stacked_on=None, create_prefix=False,
1315
use_existing_dir=False, no_tree=None,
1316
stacked_on=None, create_prefix=False, use_existing_dir=False,
1317
1318
"""Create a clone of this branch and its bzrdir.
1319
1320
:param to_transport: The transport to clone onto.
1327
1328
# XXX: Fix the bzrdir API to allow getting the branch back from the
1328
1329
# clone call. Or something. 20090224 RBC/spiv.
1329
# XXX: Should this perhaps clone colocated branches as well,
1330
# XXX: Should this perhaps clone colocated branches as well,
1330
1331
# rather than just the default branch? 20100319 JRV
1331
1332
if revision_id is None:
1332
1333
revision_id = self.last_revision()
1333
dir_to = self.controldir.clone_on_transport(
1334
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,
1335
1336
create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1336
no_tree=no_tree, tag_selector=tag_selector)
1337
1338
return dir_to.open_branch()
1339
1340
def create_checkout(self, to_location, revision_id=None,
1340
1341
lightweight=False, accelerator_tree=None,
1341
hardlink=False, recurse_nested=True):
1342
1343
"""Create a checkout of a branch.
1344
1345
:param to_location: The url to produce the checkout at
1371
1371
raise errors.AlreadyControlDirError(t.base)
1372
if (checkout.control_transport.base
1373
== self.controldir.control_transport.base):
1372
if checkout.control_transport.base == self.controldir.control_transport.base:
1374
1373
# When checking out to the same control directory,
1375
1374
# always create a lightweight checkout
1376
1375
lightweight = True
1379
1378
from_branch = checkout.set_branch_reference(target_branch=self)
1381
1380
policy = checkout.determine_repository_policy()
1382
policy.acquire_repository()
1381
repo = policy.acquire_repository()[0]
1383
1382
checkout_branch = checkout.create_branch()
1384
1383
checkout_branch.bind(self)
1385
1384
# pull up to the specified revision_id to set the initial
1392
1391
hardlink=hardlink)
1393
1392
basis_tree = tree.basis_tree()
1394
1393
with basis_tree.lock_read():
1395
for path in basis_tree.iter_references():
1396
reference_parent = tree.reference_parent(path)
1397
if reference_parent is None:
1398
warning('Branch location for %s unknown.', path)
1400
reference_parent.create_checkout(
1402
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),
1405
1401
def reconcile(self, thorough=True):
1406
"""Make sure the data stored in this branch is consistent.
1408
: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
1410
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)
1412
1420
def supports_tags(self):
1413
1421
return self._format.supports_tags()
1594
1602
raise NotImplementedError(self.network_name)
1596
1604
def open(self, controldir, name=None, _found=False, ignore_fallbacks=False,
1597
found_repository=None, possible_transports=None):
1605
found_repository=None, possible_transports=None):
1598
1606
"""Return the branch object for controldir.
1600
1608
:param controldir: A ControlDir that contains a branch.
1658
1662
Hooks.__init__(self, "breezy.branch", "Branch.hooks")
1663
self.add_hook('open',
1661
1664
"Called with the Branch object that has been opened after a "
1662
1665
"branch is opened.", (1, 8))
1666
self.add_hook('post_push',
1665
1667
"Called after a push operation completes. post_push is called "
1666
"with a breezy.branch.BranchPushResult object and only runs in "
1667
"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',
1670
1671
"Called after a pull operation completes. post_pull is called "
1671
1672
"with a breezy.branch.PullResult object and only runs in the "
1672
1673
"bzr client.", (0, 15))
1674
self.add_hook('pre_commit',
1675
1675
"Called after a commit is calculated but before it is "
1676
1676
"completed. pre_commit is called with (local, master, old_revno, "
1677
1677
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1681
1681
" future_tree is an in-memory tree obtained from "
1682
1682
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1683
1683
"tree.", (0, 91))
1684
self.add_hook('post_commit',
1686
1685
"Called in the bzr client after a commit has completed. "
1687
1686
"post_commit is called with (local, master, old_revno, old_revid, "
1688
1687
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1689
1688
"commit to a branch.", (0, 15))
1689
self.add_hook('post_uncommit',
1692
1690
"Called in the bzr client after an uncommit completes. "
1693
1691
"post_uncommit is called with (local, master, old_revno, "
1694
1692
"old_revid, new_revno, new_revid) where local is the local branch "
1695
1693
"or None, master is the target branch, and an empty branch "
1696
1694
"receives new_revno of 0, new_revid of None.", (0, 15))
1698
'pre_change_branch_tip',
1695
self.add_hook('pre_change_branch_tip',
1699
1696
"Called in bzr client and server before a change to the tip of a "
1700
1697
"branch is made. pre_change_branch_tip is called with a "
1701
1698
"breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1702
1699
"commit, uncommit will all trigger this hook.", (1, 6))
1704
'post_change_branch_tip',
1700
self.add_hook('post_change_branch_tip',
1705
1701
"Called in bzr client and server after a change to the tip of a "
1706
1702
"branch is made. post_change_branch_tip is called with a "
1707
1703
"breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1708
1704
"commit, uncommit will all trigger this hook.", (1, 4))
1710
'transform_fallback_location',
1705
self.add_hook('transform_fallback_location',
1711
1706
"Called when a stacked branch is activating its fallback "
1712
1707
"locations. transform_fallback_location is called with (branch, "
1713
1708
"url), and should return a new url. Returning the same url "
1719
1714
"multiple hooks installed for transform_fallback_location, "
1720
1715
"all are called with the url returned from the previous hook."
1721
1716
"The order is however undefined.", (1, 9))
1723
'automatic_tag_name',
1717
self.add_hook('automatic_tag_name',
1724
1718
"Called to determine an automatic tag name for a revision. "
1725
1719
"automatic_tag_name is called with (branch, revision_id) and "
1726
1720
"should return a tag name or None if no tag name could be "
1727
1721
"determined. The first non-None tag name returned will be used.",
1723
self.add_hook('post_branch_init',
1731
1724
"Called after new branch initialization completes. "
1732
1725
"post_branch_init is called with a "
1733
1726
"breezy.branch.BranchInitHookParams. "
1734
1727
"Note that init, branch and checkout (both heavyweight and "
1735
1728
"lightweight) will all trigger this hook.", (2, 2))
1729
self.add_hook('post_switch',
1738
1730
"Called after a checkout switches branch. "
1739
1731
"post_switch is called with a "
1740
1732
"breezy.branch.SwitchHookParams.", (2, 2))
1743
1736
# install the default hooks into the Branch class.
1744
1737
Branch.hooks = BranchHooks()
1850
1843
return self.__dict__ == other.__dict__
1852
1845
def __repr__(self):
1853
return "<%s for %s to (%s, %s)>" % (
1854
self.__class__.__name__, self.control_dir, self.to_branch,
1846
return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
1847
self.control_dir, self.to_branch,
1855
1848
self.revision_id)
1866
1859
def get_default(self):
1867
1860
"""Return the current default format."""
1868
if (self._default_format_key is not None
1869
and self._default_format is None):
1861
if (self._default_format_key is not None and
1862
self._default_format is None):
1870
1863
self._default_format = self.get(self._default_format_key)
1871
1864
return self._default_format
1999
1992
tag_updates = getattr(self, "tag_updates", None)
2000
1993
if not is_quiet():
2001
1994
if self.old_revid != self.new_revid:
2002
if self.new_revno is not None:
2003
note(gettext('Pushed up to revision %d.'),
2006
note(gettext('Pushed up to revision id %s.'),
2007
self.new_revid.decode('utf-8'))
1995
note(gettext('Pushed up to revision %d.') % self.new_revno)
2008
1996
if tag_updates:
2009
note(ngettext('%d tag updated.', '%d tags updated.',
2010
len(tag_updates)) % len(tag_updates))
1997
note(ngettext('%d tag updated.', '%d tags updated.', len(tag_updates)) % len(tag_updates))
2011
1998
if self.old_revid == self.new_revid and not tag_updates:
2012
1999
if not tag_conflicts:
2013
2000
note(gettext('No new revisions or tags to push.'))
2035
2022
note(gettext('checked branch {0} format {1}').format(
2036
self.branch.user_url, self.branch._format))
2023
self.branch.user_url, self.branch._format))
2037
2024
for error in self.errors:
2038
2025
note(gettext('found error:%s'), error)
2060
2047
raise NotImplementedError(klass._get_branch_formats_to_test)
2062
2049
def pull(self, overwrite=False, stop_revision=None,
2063
possible_transports=None, local=False, tag_selector=None):
2050
possible_transports=None, local=False):
2064
2051
"""Mirror source into target branch.
2066
2053
The target branch is considered to be 'local', having low latency.
2070
2057
raise NotImplementedError(self.pull)
2072
2059
def push(self, overwrite=False, stop_revision=None, lossy=False,
2073
_override_hook_source_branch=None, tag_selector=None):
2060
_override_hook_source_branch=None):
2074
2061
"""Mirror the source branch into the target branch.
2076
2063
The source branch is considered to be 'local', having low latency.
2078
2065
raise NotImplementedError(self.push)
2080
def copy_content_into(self, revision_id=None, tag_selector=None):
2067
def copy_content_into(self, revision_id=None):
2081
2068
"""Copy the content of source into target
2084
if not None, the revision history in the new branch will
2085
be truncated to end with revision_id.
2086
:param tag_selector: Optional callback that can decide
2087
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.
2089
2073
raise NotImplementedError(self.copy_content_into)
2091
def fetch(self, stop_revision=None, limit=None, lossy=False):
2075
def fetch(self, stop_revision=None, limit=None):
2092
2076
"""Fetch revisions.
2094
2078
:param stop_revision: Last revision to fetch
2095
2079
:param limit: Optional rough limit of revisions to fetch
2096
:return: FetchResult object
2098
2081
raise NotImplementedError(self.fetch)
2100
def update_references(self):
2101
"""Import reference information from source to target.
2103
raise NotImplementedError(self.update_references)
2106
2084
def _fix_overwrite_type(overwrite):
2107
2085
if isinstance(overwrite, bool):
2131
2109
return format._custom_format
2134
def copy_content_into(self, revision_id=None, tag_selector=None):
2112
def copy_content_into(self, revision_id=None):
2135
2113
"""Copy the content of source into target
2137
2115
revision_id: if not None, the revision history in the new branch will
2138
2116
be truncated to end with revision_id.
2140
2118
with self.source.lock_read(), self.target.lock_write():
2119
self.source.update_references(self.target)
2141
2120
self.source._synchronize_history(self.target, revision_id)
2142
self.update_references()
2144
2122
parent = self.source.get_parent()
2145
2123
except errors.InaccessibleParent as e:
2146
mutter('parent was not accessible to copy: %s', str(e))
2124
mutter('parent was not accessible to copy: %s', e)
2149
2127
self.target.set_parent(parent)
2150
2128
if self.source._push_should_merge_tags():
2151
self.source.tags.merge_to(self.target.tags, selector=tag_selector)
2129
self.source.tags.merge_to(self.target.tags)
2153
def fetch(self, stop_revision=None, limit=None, lossy=False):
2131
def fetch(self, stop_revision=None, limit=None):
2154
2132
if self.target.base == self.source.base:
2156
2134
with self.source.lock_read(), self.target.lock_write():
2159
2137
fetch_spec_factory.source_branch_stop_revision_id = stop_revision
2160
2138
fetch_spec_factory.source_repo = self.source.repository
2161
2139
fetch_spec_factory.target_repo = self.target.repository
2162
fetch_spec_factory.target_repo_kind = (
2163
fetch.TargetRepoKinds.PREEXISTING)
2140
fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
2164
2141
fetch_spec_factory.limit = limit
2165
2142
fetch_spec = fetch_spec_factory.make_fetch_spec()
2166
2143
return self.target.repository.fetch(
2167
self.source.repository,
2169
fetch_spec=fetch_spec)
2144
self.source.repository,
2145
fetch_spec=fetch_spec)
2171
2147
def _update_revisions(self, stop_revision=None, overwrite=False,
2173
2149
with self.source.lock_read(), self.target.lock_write():
2174
2150
other_revno, other_last_revision = self.source.last_revision_info()
2175
stop_revno = None # unknown
2151
stop_revno = None # unknown
2176
2152
if stop_revision is None:
2177
2153
stop_revision = other_last_revision
2178
2154
if _mod_revision.is_null(stop_revision):
2201
2177
if graph is None:
2202
2178
graph = self.target.repository.get_graph()
2203
2179
this_revno, this_last_revision = \
2204
self.target.last_revision_info()
2205
stop_revno = graph.find_distance_to_null(
2206
stop_revision, [(other_last_revision, other_revno),
2207
(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)])
2208
2184
self.target.set_last_revision_info(stop_revno, stop_revision)
2210
2186
def pull(self, overwrite=False, stop_revision=None,
2211
2187
possible_transports=None, run_hooks=True,
2212
_override_hook_target=None, local=False,
2188
_override_hook_target=None, local=False):
2214
2189
"""Pull from source into self, updating my master if any.
2216
2191
:param run_hooks: Private parameter - if false, this branch
2217
2192
is being called because it's the master of the primary branch,
2218
2193
so it should not run its hooks.
2220
with contextlib.ExitStack() as exit_stack:
2221
exit_stack.enter_context(self.target.lock_write())
2195
with self.target.lock_write():
2222
2196
bound_location = self.target.get_bound_location()
2223
2197
if local and not bound_location:
2224
2198
raise errors.LocalRequiresBoundBranch()
2235
2209
source_is_master = False
2236
2210
if not local and bound_location and not source_is_master:
2237
2211
# not pulling from master, so we need to update master.
2238
master_branch = self.target.get_master_branch(
2239
possible_transports)
2240
exit_stack.enter_context(master_branch.lock_write())
2242
# pull from source into master.
2244
self.source, overwrite, stop_revision, run_hooks=False,
2245
tag_selector=tag_selector)
2247
overwrite, stop_revision, _hook_master=master_branch,
2248
run_hooks=run_hooks,
2249
_override_hook_target=_override_hook_target,
2250
merge_tags_to_master=not source_is_master,
2251
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()
2253
2228
def push(self, overwrite=False, stop_revision=None, lossy=False,
2254
_override_hook_source_branch=None, tag_selector=None):
2229
_override_hook_source_branch=None):
2255
2230
"""See InterBranch.push.
2257
2232
This is the basic concrete implementation of push()
2277
2251
if bound_location and self.target.base != bound_location:
2278
2252
# there is a master branch.
2280
# XXX: Why the second check? Is it even supported for a branch
2281
# to be bound to itself? -- mbp 20070507
2254
# XXX: Why the second check? Is it even supported for a branch to
2255
# be bound to itself? -- mbp 20070507
2282
2256
master_branch = self.target.get_master_branch()
2283
2257
with master_branch.lock_write():
2284
2258
# push into the master from the source branch.
2285
2259
master_inter = InterBranch.get(self.source, master_branch)
2286
master_inter._basic_push(
2287
overwrite, stop_revision, tag_selector=tag_selector)
2288
# and push into the target branch from the source. Note
2289
# that we push from the source branch again, because it's
2290
# considered the highest bandwidth repository.
2291
result = self._basic_push(
2292
overwrite, stop_revision, tag_selector=tag_selector)
2260
master_inter._basic_push(overwrite, stop_revision)
2261
# and push into the target branch from the source. Note that
2262
# we push from the source branch again, because it's considered
2263
# the highest bandwidth repository.
2264
result = self._basic_push(overwrite, stop_revision)
2293
2265
result.master_branch = master_branch
2294
2266
result.local_branch = self.target
2297
2269
master_branch = None
2298
2270
# no master branch
2299
result = self._basic_push(
2300
overwrite, stop_revision, tag_selector=tag_selector)
2271
result = self._basic_push(overwrite, stop_revision)
2301
2272
# TODO: Why set master_branch and local_branch if there's no
2302
2273
# binding? Maybe cleaner to just leave them unset? -- mbp
2309
def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2280
def _basic_push(self, overwrite, stop_revision):
2310
2281
"""Basic implementation of push without bound branches or hooks.
2312
2283
Must be called with source read locked and target write locked.
2315
2286
result.source_branch = self.source
2316
2287
result.target_branch = self.target
2317
2288
result.old_revno, result.old_revid = self.target.last_revision_info()
2289
self.source.update_references(self.target)
2318
2290
overwrite = _fix_overwrite_type(overwrite)
2319
2291
if result.old_revid != stop_revision:
2320
2292
# We assume that during 'push' this repository is closer than
2322
2294
graph = self.source.repository.get_graph(self.target.repository)
2323
self._update_revisions(
2324
stop_revision, overwrite=("history" in overwrite), graph=graph)
2295
self._update_revisions(stop_revision,
2296
overwrite=("history" in overwrite),
2325
2298
if self.source._push_should_merge_tags():
2326
2299
result.tag_updates, result.tag_conflicts = (
2327
2300
self.source.tags.merge_to(
2328
self.target.tags, "tags" in overwrite, selector=tag_selector))
2329
self.update_references()
2301
self.target.tags, "tags" in overwrite))
2330
2302
result.new_revno, result.new_revid = self.target.last_revision_info()
2333
2305
def _pull(self, overwrite=False, stop_revision=None,
2334
possible_transports=None, _hook_master=None, run_hooks=True,
2335
_override_hook_target=None, local=False,
2336
merge_tags_to_master=True, tag_selector=None):
2306
possible_transports=None, _hook_master=None, run_hooks=True,
2307
_override_hook_target=None, local=False,
2308
merge_tags_to_master=True):
2337
2309
"""See Branch.pull.
2339
2311
This function is the core worker, used by GenericInterBranch.pull to
2362
2334
with self.source.lock_read():
2363
2335
# We assume that during 'pull' the target repository is closer than
2364
2336
# the source one.
2337
self.source.update_references(self.target)
2365
2338
graph = self.target.repository.get_graph(self.source.repository)
2366
# TODO: Branch formats should have a flag that indicates
2339
# TODO: Branch formats should have a flag that indicates
2367
2340
# that revno's are expensive, and pull() should honor that flag.
2368
2341
# -- JRV20090506
2369
2342
result.old_revno, result.old_revid = \
2370
2343
self.target.last_revision_info()
2371
2344
overwrite = _fix_overwrite_type(overwrite)
2372
self._update_revisions(
2373
stop_revision, overwrite=("history" in overwrite), graph=graph)
2374
# TODO: The old revid should be specified when merging tags,
2375
# so a tags implementation that versions tags can only
2345
self._update_revisions(stop_revision,
2346
overwrite=("history" in overwrite),
2348
# TODO: The old revid should be specified when merging tags,
2349
# so a tags implementation that versions tags can only
2376
2350
# pull in the most recent changes. -- JRV20090506
2377
2351
result.tag_updates, result.tag_conflicts = (
2378
self.source.tags.merge_to(
2379
self.target.tags, "tags" in overwrite,
2380
ignore_master=not merge_tags_to_master,
2381
selector=tag_selector))
2382
self.update_references()
2383
result.new_revno, result.new_revid = (
2384
self.target.last_revision_info())
2352
self.source.tags.merge_to(self.target.tags,
2353
"tags" in overwrite,
2354
ignore_master=not merge_tags_to_master))
2355
result.new_revno, result.new_revid = self.target.last_revision_info()
2385
2356
if _hook_master:
2386
2357
result.master_branch = _hook_master
2387
2358
result.local_branch = result.target_branch
2396
def update_references(self):
2397
if not getattr(self.source._format, 'supports_reference_locations', False):
2399
reference_dict = self.source._get_all_reference_info()
2400
if len(reference_dict) == 0:
2402
old_base = self.source.base
2403
new_base = self.target.base
2404
target_reference_dict = self.target._get_all_reference_info()
2405
for tree_path, (branch_location, file_id) in reference_dict.items():
2407
branch_location = urlutils.rebase_url(branch_location,
2409
except urlutils.InvalidRebaseURLs:
2410
# Fall back to absolute URL
2411
branch_location = urlutils.join(old_base, branch_location)
2412
target_reference_dict.setdefault(
2413
tree_path, (branch_location, file_id))
2414
self.target._set_all_reference_info(target_reference_dict)
2417
2368
InterBranch.register_optimiser(GenericInterBranch)