189
239
raise NotImplementedError(self.get_physical_lock_status)
242
def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
243
"""Return the revision_id for a dotted revno.
245
:param revno: a tuple like (1,) or (1,1,2)
246
:param _cache_reverse: a private parameter enabling storage
247
of the reverse mapping in a top level cache. (This should
248
only be done in selective circumstances as we want to
249
avoid having the mapping cached multiple times.)
250
:return: the revision_id
251
:raises errors.NoSuchRevision: if the revno doesn't exist
253
rev_id = self._do_dotted_revno_to_revision_id(revno)
255
self._partial_revision_id_to_revno_cache[rev_id] = revno
258
def _do_dotted_revno_to_revision_id(self, revno):
259
"""Worker function for dotted_revno_to_revision_id.
261
Subclasses should override this if they wish to
262
provide a more efficient implementation.
265
return self.get_rev_id(revno[0])
266
revision_id_to_revno = self.get_revision_id_to_revno_map()
267
revision_ids = [revision_id for revision_id, this_revno
268
in revision_id_to_revno.iteritems()
269
if revno == this_revno]
270
if len(revision_ids) == 1:
271
return revision_ids[0]
273
revno_str = '.'.join(map(str, revno))
274
raise errors.NoSuchRevision(self, revno_str)
277
def revision_id_to_dotted_revno(self, revision_id):
278
"""Given a revision id, return its dotted revno.
280
:return: a tuple like (1,) or (400,1,3).
282
return self._do_revision_id_to_dotted_revno(revision_id)
284
def _do_revision_id_to_dotted_revno(self, revision_id):
285
"""Worker function for revision_id_to_revno."""
286
# Try the caches if they are loaded
287
result = self._partial_revision_id_to_revno_cache.get(revision_id)
288
if result is not None:
290
if self._revision_id_to_revno_cache:
291
result = self._revision_id_to_revno_cache.get(revision_id)
293
raise errors.NoSuchRevision(self, revision_id)
294
# Try the mainline as it's optimised
296
revno = self.revision_id_to_revno(revision_id)
298
except errors.NoSuchRevision:
299
# We need to load and use the full revno map after all
300
result = self.get_revision_id_to_revno_map().get(revision_id)
302
raise errors.NoSuchRevision(self, revision_id)
192
306
def get_revision_id_to_revno_map(self):
193
307
"""Return the revision_id => dotted revno map.
219
333
:return: A dictionary mapping revision_id => dotted revno.
221
last_revision = self.last_revision()
222
revision_graph = repository._old_get_graph(self.repository,
224
merge_sorted_revisions = tsort.merge_sort(
229
335
revision_id_to_revno = dict((rev_id, revno)
230
for seq_num, rev_id, depth, revno, end_of_merge
231
in merge_sorted_revisions)
336
for rev_id, depth, revno, end_of_merge
337
in self.iter_merge_sorted_revisions())
232
338
return revision_id_to_revno
341
def iter_merge_sorted_revisions(self, start_revision_id=None,
342
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
343
"""Walk the revisions for a branch in merge sorted order.
345
Merge sorted order is the output from a merge-aware,
346
topological sort, i.e. all parents come before their
347
children going forward; the opposite for reverse.
349
:param start_revision_id: the revision_id to begin walking from.
350
If None, the branch tip is used.
351
:param stop_revision_id: the revision_id to terminate the walk
352
after. If None, the rest of history is included.
353
:param stop_rule: if stop_revision_id is not None, the precise rule
354
to use for termination:
355
* 'exclude' - leave the stop revision out of the result (default)
356
* 'include' - the stop revision is the last item in the result
357
* 'with-merges' - include the stop revision and all of its
358
merged revisions in the result
359
:param direction: either 'reverse' or 'forward':
360
* reverse means return the start_revision_id first, i.e.
361
start at the most recent revision and go backwards in history
362
* forward returns tuples in the opposite order to reverse.
363
Note in particular that forward does *not* do any intelligent
364
ordering w.r.t. depth as some clients of this API may like.
365
(If required, that ought to be done at higher layers.)
367
:return: an iterator over (revision_id, depth, revno, end_of_merge)
370
* revision_id: the unique id of the revision
371
* depth: How many levels of merging deep this node has been
373
* revno_sequence: This field provides a sequence of
374
revision numbers for all revisions. The format is:
375
(REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
376
branch that the revno is on. From left to right the REVNO numbers
377
are the sequence numbers within that branch of the revision.
378
* end_of_merge: When True the next node (earlier in history) is
379
part of a different merge.
381
# Note: depth and revno values are in the context of the branch so
382
# we need the full graph to get stable numbers, regardless of the
384
if self._merge_sorted_revisions_cache is None:
385
last_revision = self.last_revision()
386
graph = self.repository.get_graph()
387
parent_map = dict(((key, value) for key, value in
388
graph.iter_ancestry([last_revision]) if value is not None))
389
revision_graph = repository._strip_NULL_ghosts(parent_map)
390
revs = tsort.merge_sort(revision_graph, last_revision, None,
392
# Drop the sequence # before caching
393
self._merge_sorted_revisions_cache = [r[1:] for r in revs]
395
filtered = self._filter_merge_sorted_revisions(
396
self._merge_sorted_revisions_cache, start_revision_id,
397
stop_revision_id, stop_rule)
398
if direction == 'reverse':
400
if direction == 'forward':
401
return reversed(list(filtered))
403
raise ValueError('invalid direction %r' % direction)
405
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
406
start_revision_id, stop_revision_id, stop_rule):
407
"""Iterate over an inclusive range of sorted revisions."""
408
rev_iter = iter(merge_sorted_revisions)
409
if start_revision_id is not None:
410
for rev_id, depth, revno, end_of_merge in rev_iter:
411
if rev_id != start_revision_id:
414
# The decision to include the start or not
415
# depends on the stop_rule if a stop is provided
417
iter([(rev_id, depth, revno, end_of_merge)]),
420
if stop_revision_id is None:
421
for rev_id, depth, revno, end_of_merge in rev_iter:
422
yield rev_id, depth, revno, end_of_merge
423
elif stop_rule == 'exclude':
424
for rev_id, depth, revno, end_of_merge in rev_iter:
425
if rev_id == stop_revision_id:
427
yield rev_id, depth, revno, end_of_merge
428
elif stop_rule == 'include':
429
for rev_id, depth, revno, end_of_merge in rev_iter:
430
yield rev_id, depth, revno, end_of_merge
431
if rev_id == stop_revision_id:
433
elif stop_rule == 'with-merges':
434
stop_rev = self.repository.get_revision(stop_revision_id)
435
if stop_rev.parent_ids:
436
left_parent = stop_rev.parent_ids[0]
438
left_parent = _mod_revision.NULL_REVISION
439
for rev_id, depth, revno, end_of_merge in rev_iter:
440
if rev_id == left_parent:
442
yield rev_id, depth, revno, end_of_merge
444
raise ValueError('invalid stop_rule %r' % stop_rule)
234
446
def leave_lock_in_place(self):
235
447
"""Tell this branch object not to release the physical lock when this
236
448
object is unlocked.
238
450
If lock_write doesn't return a token, then this method is not supported.
240
452
self.control_files.leave_in_place()
503
717
information. This can be None.
508
other_revno, other_last_revision = other.last_revision_info()
509
stop_revno = None # unknown
510
if stop_revision is None:
511
stop_revision = other_last_revision
512
if _mod_revision.is_null(stop_revision):
513
# if there are no commits, we're done.
515
stop_revno = other_revno
517
# what's the current last revision, before we fetch [and change it
519
last_rev = _mod_revision.ensure_null(self.last_revision())
520
# we fetch here so that we don't process data twice in the common
521
# case of having something to pull, and so that the check for
522
# already merged can operate on the just fetched graph, which will
523
# be cached in memory.
524
self.fetch(other, stop_revision)
525
# Check to see if one is an ancestor of the other
528
graph = self.repository.get_graph()
529
if self._check_if_descendant_or_diverged(
530
stop_revision, last_rev, graph, other):
531
# stop_revision is a descendant of last_rev, but we aren't
532
# overwriting, so we're done.
534
if stop_revno is None:
536
graph = self.repository.get_graph()
537
this_revno, this_last_revision = self.last_revision_info()
538
stop_revno = graph.find_distance_to_null(stop_revision,
539
[(other_last_revision, other_revno),
540
(this_last_revision, this_revno)])
541
self.set_last_revision_info(stop_revno, stop_revision)
720
return InterBranch.get(other, self).update_revisions(stop_revision,
723
def import_last_revision_info(self, source_repo, revno, revid):
724
"""Set the last revision info, importing from another repo if necessary.
726
This is used by the bound branch code to upload a revision to
727
the master branch first before updating the tip of the local branch.
729
:param source_repo: Source repository to optionally fetch from
730
:param revno: Revision number of the new tip
731
:param revid: Revision id of the new tip
733
if not self.repository.has_same_location(source_repo):
734
self.repository.fetch(source_repo, revision_id=revid)
735
self.set_last_revision_info(revno, revid)
545
737
def revision_id_to_revno(self, revision_id):
546
738
"""Given a revision id, return its revno"""
654
859
"""Set a new push location for this branch."""
655
860
raise NotImplementedError(self.set_push_location)
862
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
863
"""Run the post_change_branch_tip hooks."""
864
hooks = Branch.hooks['post_change_branch_tip']
867
new_revno, new_revid = self.last_revision_info()
868
params = ChangeBranchTipParams(
869
self, old_revno, new_revno, old_revid, new_revid)
873
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
874
"""Run the pre_change_branch_tip hooks."""
875
hooks = Branch.hooks['pre_change_branch_tip']
878
old_revno, old_revid = self.last_revision_info()
879
params = ChangeBranchTipParams(
880
self, old_revno, new_revno, old_revid, new_revid)
884
except errors.TipChangeRejected:
887
exc_info = sys.exc_info()
888
hook_name = Branch.hooks.get_hook_name(hook)
889
raise errors.HookFailed(
890
'pre_change_branch_tip', hook_name, exc_info)
657
892
def set_parent(self, url):
658
893
raise NotImplementedError(self.set_parent)
660
895
@needs_write_lock
661
896
def update(self):
662
"""Synchronise this branch with the master branch if any.
897
"""Synchronise this branch with the master branch if any.
664
899
:return: None or the last_revision pivoted out during the update.
1084
1370
Hooks.__init__(self)
1085
# Introduced in 0.15:
1086
# invoked whenever the revision history has been set
1087
# with set_revision_history. The api signature is
1088
# (branch, revision_history), and the branch will
1091
# Invoked after a branch is opened. The api signature is (branch).
1093
# invoked after a push operation completes.
1094
# the api signature is
1096
# containing the members
1097
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1098
# where local is the local target branch or None, master is the target
1099
# master branch, and the rest should be self explanatory. The source
1100
# is read locked and the target branches write locked. Source will
1101
# be the local low-latency branch.
1102
self['post_push'] = []
1103
# invoked after a pull operation completes.
1104
# the api signature is
1106
# containing the members
1107
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1108
# where local is the local branch or None, master is the target
1109
# master branch, and the rest should be self explanatory. The source
1110
# is read locked and the target branches write locked. The local
1111
# branch is the low-latency branch.
1112
self['post_pull'] = []
1113
# invoked before a commit operation takes place.
1114
# the api signature is
1115
# (local, master, old_revno, old_revid, future_revno, future_revid,
1116
# tree_delta, future_tree).
1117
# old_revid is NULL_REVISION for the first commit to a branch
1118
# tree_delta is a TreeDelta object describing changes from the basis
1119
# revision, hooks MUST NOT modify this delta
1120
# future_tree is an in-memory tree obtained from
1121
# CommitBuilder.revision_tree() and hooks MUST NOT modify this tree
1122
self['pre_commit'] = []
1123
# invoked after a commit operation completes.
1124
# the api signature is
1125
# (local, master, old_revno, old_revid, new_revno, new_revid)
1126
# old_revid is NULL_REVISION for the first commit to a branch.
1127
self['post_commit'] = []
1128
# invoked after a uncommit operation completes.
1129
# the api signature is
1130
# (local, master, old_revno, old_revid, new_revno, new_revid) where
1131
# local is the local branch or None, master is the target branch,
1132
# and an empty branch recieves new_revno of 0, new_revid of None.
1133
self['post_uncommit'] = []
1135
# Invoked before the tip of a branch changes.
1136
# the api signature is
1137
# (params) where params is a ChangeBranchTipParams with the members
1138
# (branch, old_revno, new_revno, old_revid, new_revid)
1139
self['pre_change_branch_tip'] = []
1141
# Invoked after the tip of a branch changes.
1142
# the api signature is
1143
# (params) where params is a ChangeBranchTipParams with the members
1144
# (branch, old_revno, new_revno, old_revid, new_revid)
1145
self['post_change_branch_tip'] = []
1147
# Invoked when a stacked branch activates its fallback locations and
1148
# allows the transformation of the url of said location.
1149
# the api signature is
1150
# (branch, url) where branch is the branch having its fallback
1151
# location activated and url is the url for the fallback location.
1152
# The hook should return a url.
1153
self['transform_fallback_location'] = []
1371
self.create_hook(HookPoint('set_rh',
1372
"Invoked whenever the revision history has been set via "
1373
"set_revision_history. The api signature is (branch, "
1374
"revision_history), and the branch will be write-locked. "
1375
"The set_rh hook can be expensive for bzr to trigger, a better "
1376
"hook to use is Branch.post_change_branch_tip.", (0, 15), None))
1377
self.create_hook(HookPoint('open',
1378
"Called with the Branch object that has been opened after a "
1379
"branch is opened.", (1, 8), None))
1380
self.create_hook(HookPoint('post_push',
1381
"Called after a push operation completes. post_push is called "
1382
"with a bzrlib.branch.BranchPushResult object and only runs in the "
1383
"bzr client.", (0, 15), None))
1384
self.create_hook(HookPoint('post_pull',
1385
"Called after a pull operation completes. post_pull is called "
1386
"with a bzrlib.branch.PullResult object and only runs in the "
1387
"bzr client.", (0, 15), None))
1388
self.create_hook(HookPoint('pre_commit',
1389
"Called after a commit is calculated but before it is is "
1390
"completed. pre_commit is called with (local, master, old_revno, "
1391
"old_revid, future_revno, future_revid, tree_delta, future_tree"
1392
"). old_revid is NULL_REVISION for the first commit to a branch, "
1393
"tree_delta is a TreeDelta object describing changes from the "
1394
"basis revision. hooks MUST NOT modify this delta. "
1395
" future_tree is an in-memory tree obtained from "
1396
"CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1397
"tree.", (0,91), None))
1398
self.create_hook(HookPoint('post_commit',
1399
"Called in the bzr client after a commit has completed. "
1400
"post_commit is called with (local, master, old_revno, old_revid, "
1401
"new_revno, new_revid). old_revid is NULL_REVISION for the first "
1402
"commit to a branch.", (0, 15), None))
1403
self.create_hook(HookPoint('post_uncommit',
1404
"Called in the bzr client after an uncommit completes. "
1405
"post_uncommit is called with (local, master, old_revno, "
1406
"old_revid, new_revno, new_revid) where local is the local branch "
1407
"or None, master is the target branch, and an empty branch "
1408
"recieves new_revno of 0, new_revid of None.", (0, 15), None))
1409
self.create_hook(HookPoint('pre_change_branch_tip',
1410
"Called in bzr client and server before a change to the tip of a "
1411
"branch is made. pre_change_branch_tip is called with a "
1412
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1413
"commit, uncommit will all trigger this hook.", (1, 6), None))
1414
self.create_hook(HookPoint('post_change_branch_tip',
1415
"Called in bzr client and server after a change to the tip of a "
1416
"branch is made. post_change_branch_tip is called with a "
1417
"bzrlib.branch.ChangeBranchTipParams. Note that push, pull, "
1418
"commit, uncommit will all trigger this hook.", (1, 4), None))
1419
self.create_hook(HookPoint('transform_fallback_location',
1420
"Called when a stacked branch is activating its fallback "
1421
"locations. transform_fallback_location is called with (branch, "
1422
"url), and should return a new url. Returning the same url "
1423
"allows it to be used as-is, returning a different one can be "
1424
"used to cause the branch to stack on a closer copy of that "
1425
"fallback_location. Note that the branch cannot have history "
1426
"accessing methods called on it during this hook because the "
1427
"fallback locations have not been activated. When there are "
1428
"multiple hooks installed for transform_fallback_location, "
1429
"all are called with the url returned from the previous hook."
1430
"The order is however undefined.", (1, 9), None))
1156
1433
# install the default hooks into the Branch class.
2524
2759
target.unlock()
2763
class InterBranch(InterObject):
2764
"""This class represents operations taking place between two branches.
2766
Its instances have methods like pull() and push() and contain
2767
references to the source and target repositories these operations
2768
can be carried out on.
2772
"""The available optimised InterBranch types."""
2775
def _get_branch_formats_to_test():
2776
"""Return a tuple with the Branch formats to use when testing."""
2777
raise NotImplementedError(self._get_branch_formats_to_test)
2779
def update_revisions(self, stop_revision=None, overwrite=False,
2781
"""Pull in new perfect-fit revisions.
2783
:param stop_revision: Updated until the given revision
2784
:param overwrite: Always set the branch pointer, rather than checking
2785
to see if it is a proper descendant.
2786
:param graph: A Graph object that can be used to query history
2787
information. This can be None.
2790
raise NotImplementedError(self.update_revisions)
2793
class GenericInterBranch(InterBranch):
2794
"""InterBranch implementation that uses public Branch functions.
2798
def _get_branch_formats_to_test():
2799
return BranchFormat._default_format, BranchFormat._default_format
2801
def update_revisions(self, stop_revision=None, overwrite=False,
2803
"""See InterBranch.update_revisions()."""
2804
self.source.lock_read()
2806
other_revno, other_last_revision = self.source.last_revision_info()
2807
stop_revno = None # unknown
2808
if stop_revision is None:
2809
stop_revision = other_last_revision
2810
if _mod_revision.is_null(stop_revision):
2811
# if there are no commits, we're done.
2813
stop_revno = other_revno
2815
# what's the current last revision, before we fetch [and change it
2817
last_rev = _mod_revision.ensure_null(self.target.last_revision())
2818
# we fetch here so that we don't process data twice in the common
2819
# case of having something to pull, and so that the check for
2820
# already merged can operate on the just fetched graph, which will
2821
# be cached in memory.
2822
self.target.fetch(self.source, stop_revision)
2823
# Check to see if one is an ancestor of the other
2826
graph = self.target.repository.get_graph()
2827
if self.target._check_if_descendant_or_diverged(
2828
stop_revision, last_rev, graph, self.source):
2829
# stop_revision is a descendant of last_rev, but we aren't
2830
# overwriting, so we're done.
2832
if stop_revno is None:
2834
graph = self.target.repository.get_graph()
2835
this_revno, this_last_revision = \
2836
self.target.last_revision_info()
2837
stop_revno = graph.find_distance_to_null(stop_revision,
2838
[(other_last_revision, other_revno),
2839
(this_last_revision, this_revno)])
2840
self.target.set_last_revision_info(stop_revno, stop_revision)
2842
self.source.unlock()
2845
def is_compatible(self, source, target):
2846
# GenericBranch uses the public API, so always compatible
2850
InterBranch.register_optimiser(GenericInterBranch)