172
183
def is_locked(self):
173
184
raise NotImplementedError(self.is_locked)
186
def _lefthand_history(self, revision_id, last_rev=None,
188
if 'evil' in debug.debug_flags:
189
mutter_callsite(4, "_lefthand_history scales with history.")
190
# stop_revision must be a descendant of last_revision
191
graph = self.repository.get_graph()
192
if last_rev is not None:
193
if not graph.is_ancestor(last_rev, revision_id):
194
# our previous tip is not merged into stop_revision
195
raise errors.DivergedBranches(self, other_branch)
196
# make a new revision history from the graph
197
parents_map = graph.get_parent_map([revision_id])
198
if revision_id not in parents_map:
199
raise errors.NoSuchRevision(self, revision_id)
200
current_rev_id = revision_id
202
check_not_reserved_id = _mod_revision.check_not_reserved_id
203
# Do not include ghosts or graph origin in revision_history
204
while (current_rev_id in parents_map and
205
len(parents_map[current_rev_id]) > 0):
206
check_not_reserved_id(current_rev_id)
207
new_history.append(current_rev_id)
208
current_rev_id = parents_map[current_rev_id][0]
209
parents_map = graph.get_parent_map([current_rev_id])
210
new_history.reverse()
175
213
def lock_write(self):
176
214
raise NotImplementedError(self.lock_write)
189
227
raise NotImplementedError(self.get_physical_lock_status)
230
def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
231
"""Return the revision_id for a dotted revno.
233
:param revno: a tuple like (1,) or (1,1,2)
234
:param _cache_reverse: a private parameter enabling storage
235
of the reverse mapping in a top level cache. (This should
236
only be done in selective circumstances as we want to
237
avoid having the mapping cached multiple times.)
238
:return: the revision_id
239
:raises errors.NoSuchRevision: if the revno doesn't exist
241
rev_id = self._do_dotted_revno_to_revision_id(revno)
243
self._partial_revision_id_to_revno_cache[rev_id] = revno
246
def _do_dotted_revno_to_revision_id(self, revno):
247
"""Worker function for dotted_revno_to_revision_id.
249
Subclasses should override this if they wish to
250
provide a more efficient implementation.
253
return self.get_rev_id(revno[0])
254
revision_id_to_revno = self.get_revision_id_to_revno_map()
255
revision_ids = [revision_id for revision_id, this_revno
256
in revision_id_to_revno.iteritems()
257
if revno == this_revno]
258
if len(revision_ids) == 1:
259
return revision_ids[0]
261
revno_str = '.'.join(map(str, revno))
262
raise errors.NoSuchRevision(self, revno_str)
265
def revision_id_to_dotted_revno(self, revision_id):
266
"""Given a revision id, return its dotted revno.
268
:return: a tuple like (1,) or (400,1,3).
270
return self._do_revision_id_to_dotted_revno(revision_id)
272
def _do_revision_id_to_dotted_revno(self, revision_id):
273
"""Worker function for revision_id_to_revno."""
274
# Try the caches if they are loaded
275
result = self._partial_revision_id_to_revno_cache.get(revision_id)
276
if result is not None:
278
if self._revision_id_to_revno_cache:
279
result = self._revision_id_to_revno_cache.get(revision_id)
281
raise errors.NoSuchRevision(self, revision_id)
282
# Try the mainline as it's optimised
284
revno = self.revision_id_to_revno(revision_id)
286
except errors.NoSuchRevision:
287
# We need to load and use the full revno map after all
288
result = self.get_revision_id_to_revno_map().get(revision_id)
290
raise errors.NoSuchRevision(self, revision_id)
192
294
def get_revision_id_to_revno_map(self):
193
295
"""Return the revision_id => dotted revno map.
219
321
: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
323
revision_id_to_revno = dict((rev_id, revno)
230
for seq_num, rev_id, depth, revno, end_of_merge
231
in merge_sorted_revisions)
324
for rev_id, depth, revno, end_of_merge
325
in self.iter_merge_sorted_revisions())
232
326
return revision_id_to_revno
329
def iter_merge_sorted_revisions(self, start_revision_id=None,
330
stop_revision_id=None, stop_rule='exclude', direction='reverse'):
331
"""Walk the revisions for a branch in merge sorted order.
333
Merge sorted order is the output from a merge-aware,
334
topological sort, i.e. all parents come before their
335
children going forward; the opposite for reverse.
337
:param start_revision_id: the revision_id to begin walking from.
338
If None, the branch tip is used.
339
:param stop_revision_id: the revision_id to terminate the walk
340
after. If None, the rest of history is included.
341
:param stop_rule: if stop_revision_id is not None, the precise rule
342
to use for termination:
343
* 'exclude' - leave the stop revision out of the result (default)
344
* 'include' - the stop revision is the last item in the result
345
* 'with-merges' - include the stop revision and all of its
346
merged revisions in the result
347
:param direction: either 'reverse' or 'forward':
348
* reverse means return the start_revision_id first, i.e.
349
start at the most recent revision and go backwards in history
350
* forward returns tuples in the opposite order to reverse.
351
Note in particular that forward does *not* do any intelligent
352
ordering w.r.t. depth as some clients of this API may like.
353
(If required, that ought to be done at higher layers.)
355
:return: an iterator over (revision_id, depth, revno, end_of_merge)
358
* revision_id: the unique id of the revision
359
* depth: How many levels of merging deep this node has been
361
* revno_sequence: This field provides a sequence of
362
revision numbers for all revisions. The format is:
363
(REVNO, BRANCHNUM, BRANCHREVNO). BRANCHNUM is the number of the
364
branch that the revno is on. From left to right the REVNO numbers
365
are the sequence numbers within that branch of the revision.
366
* end_of_merge: When True the next node (earlier in history) is
367
part of a different merge.
369
# Note: depth and revno values are in the context of the branch so
370
# we need the full graph to get stable numbers, regardless of the
372
if self._merge_sorted_revisions_cache is None:
373
last_revision = self.last_revision()
374
graph = self.repository.get_graph()
375
parent_map = dict(((key, value) for key, value in
376
graph.iter_ancestry([last_revision]) if value is not None))
377
revision_graph = repository._strip_NULL_ghosts(parent_map)
378
revs = tsort.merge_sort(revision_graph, last_revision, None,
380
# Drop the sequence # before caching
381
self._merge_sorted_revisions_cache = [r[1:] for r in revs]
383
filtered = self._filter_merge_sorted_revisions(
384
self._merge_sorted_revisions_cache, start_revision_id,
385
stop_revision_id, stop_rule)
386
if direction == 'reverse':
388
if direction == 'forward':
389
return reversed(list(filtered))
391
raise ValueError('invalid direction %r' % direction)
393
def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
394
start_revision_id, stop_revision_id, stop_rule):
395
"""Iterate over an inclusive range of sorted revisions."""
396
rev_iter = iter(merge_sorted_revisions)
397
if start_revision_id is not None:
398
for rev_id, depth, revno, end_of_merge in rev_iter:
399
if rev_id != start_revision_id:
402
# The decision to include the start or not
403
# depends on the stop_rule if a stop is provided
405
iter([(rev_id, depth, revno, end_of_merge)]),
408
if stop_revision_id is None:
409
for rev_id, depth, revno, end_of_merge in rev_iter:
410
yield rev_id, depth, revno, end_of_merge
411
elif stop_rule == 'exclude':
412
for rev_id, depth, revno, end_of_merge in rev_iter:
413
if rev_id == stop_revision_id:
415
yield rev_id, depth, revno, end_of_merge
416
elif stop_rule == 'include':
417
for rev_id, depth, revno, end_of_merge in rev_iter:
418
yield rev_id, depth, revno, end_of_merge
419
if rev_id == stop_revision_id:
421
elif stop_rule == 'with-merges':
422
stop_rev = self.repository.get_revision(stop_revision_id)
423
if stop_rev.parent_ids:
424
left_parent = stop_rev.parent_ids[0]
426
left_parent = _mod_revision.NULL_REVISION
427
for rev_id, depth, revno, end_of_merge in rev_iter:
428
if rev_id == left_parent:
430
yield rev_id, depth, revno, end_of_merge
432
raise ValueError('invalid stop_rule %r' % stop_rule)
234
434
def leave_lock_in_place(self):
235
435
"""Tell this branch object not to release the physical lock when this
236
436
object is unlocked.
238
438
If lock_write doesn't return a token, then this method is not supported.
240
440
self.control_files.leave_in_place()
654
855
"""Set a new push location for this branch."""
655
856
raise NotImplementedError(self.set_push_location)
858
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
859
"""Run the post_change_branch_tip hooks."""
860
hooks = Branch.hooks['post_change_branch_tip']
863
new_revno, new_revid = self.last_revision_info()
864
params = ChangeBranchTipParams(
865
self, old_revno, new_revno, old_revid, new_revid)
869
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
870
"""Run the pre_change_branch_tip hooks."""
871
hooks = Branch.hooks['pre_change_branch_tip']
874
old_revno, old_revid = self.last_revision_info()
875
params = ChangeBranchTipParams(
876
self, old_revno, new_revno, old_revid, new_revid)
880
except errors.TipChangeRejected:
883
exc_info = sys.exc_info()
884
hook_name = Branch.hooks.get_hook_name(hook)
885
raise errors.HookFailed(
886
'pre_change_branch_tip', hook_name, exc_info)
657
888
def set_parent(self, url):
658
889
raise NotImplementedError(self.set_parent)
660
891
@needs_write_lock
661
892
def update(self):
662
"""Synchronise this branch with the master branch if any.
893
"""Synchronise this branch with the master branch if any.
664
895
:return: None or the last_revision pivoted out during the update.
1626
1898
new_history = rev.get_history(self.repository)[1:]
1627
1899
destination.set_revision_history(new_history)
1629
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1630
"""Run the pre_change_branch_tip hooks."""
1631
hooks = Branch.hooks['pre_change_branch_tip']
1634
old_revno, old_revid = self.last_revision_info()
1635
params = ChangeBranchTipParams(
1636
self, old_revno, new_revno, old_revid, new_revid)
1640
except errors.TipChangeRejected:
1643
exc_info = sys.exc_info()
1644
hook_name = Branch.hooks.get_hook_name(hook)
1645
raise errors.HookFailed(
1646
'pre_change_branch_tip', hook_name, exc_info)
1648
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1649
"""Run the post_change_branch_tip hooks."""
1650
hooks = Branch.hooks['post_change_branch_tip']
1653
new_revno, new_revid = self.last_revision_info()
1654
params = ChangeBranchTipParams(
1655
self, old_revno, new_revno, old_revid, new_revid)
1659
1901
@needs_write_lock
1660
1902
def set_last_revision_info(self, revno, revision_id):
1661
1903
"""Set the last revision of this branch.
1687
def _lefthand_history(self, revision_id, last_rev=None,
1689
if 'evil' in debug.debug_flags:
1690
mutter_callsite(4, "_lefthand_history scales with history.")
1691
# stop_revision must be a descendant of last_revision
1692
graph = self.repository.get_graph()
1693
if last_rev is not None:
1694
if not graph.is_ancestor(last_rev, revision_id):
1695
# our previous tip is not merged into stop_revision
1696
raise errors.DivergedBranches(self, other_branch)
1697
# make a new revision history from the graph
1698
parents_map = graph.get_parent_map([revision_id])
1699
if revision_id not in parents_map:
1700
raise errors.NoSuchRevision(self, revision_id)
1701
current_rev_id = revision_id
1703
check_not_reserved_id = _mod_revision.check_not_reserved_id
1704
# Do not include ghosts or graph origin in revision_history
1705
while (current_rev_id in parents_map and
1706
len(parents_map[current_rev_id]) > 0):
1707
check_not_reserved_id(current_rev_id)
1708
new_history.append(current_rev_id)
1709
current_rev_id = parents_map[current_rev_id][0]
1710
parents_map = graph.get_parent_map([current_rev_id])
1711
new_history.reverse()
1714
1929
@needs_write_lock
1715
1930
def generate_revision_history(self, revision_id, last_rev=None,
1716
1931
other_branch=None):