##// END OF EJS Templates
deltas: set estimated compression upper bound to "3x" instead of "10x"...
deltas: set estimated compression upper bound to "3x" instead of "10x" In pratice, we very rarely observer compression better than "3x" on manifest deltas. Having a more aggressive estimate significantly helps our pathological use case on a private repository. Here are a comparison of timings using different upper bound. Estimated compression | ø | ×10 | ×5 | ×3 | timing | 14.11 | 2.61 | 1.96 | 1.53 | We also tested the impact of this series on an array of public repositories. This shown no impact in either size nor timing. Full data set below for those interested. Size ---- Regarding size, not significant impact have been noticed on neither public nor private repositories. Here are the number we gathered on public repositories: zlib/upperbound | no | 10x | 5x | 3x mercurial | 5 875 730 | 5 875 730 | 5 875 730 | 5 875 730 pypy | 27 782 913 | 27 782 913 | 27 782 913 | 27 782 913 netbeans | 159 161 207 | 159 161 207 | 159 161 207 | 159 959 879 (+0.5%) mozilla-central | 323 841 642 | 323 841 642 | 323 841 642 | 319 867 519 (-2.5%) mozilla-try | 746 649 123 | 746 649 123 | 746 649 123 | 741 155 568 (-0.7%) private-repo | 1 485 287 294 | 1 485 287 294 | 1 485 287 294 | 1 409 248 382 (-5.1%) zstd/upperbound | no | 10x | 5x | 3x mercurial | 5 895 206 | 5 895 206 | 5 895 206 | 5 895 206 pypy | 28 689 230 | 28 689 230 | 28 689 230 | 28 689 230 netbeans | 157 636 387 | 157 636 387 | 157 636 387 | 159 692 678 (+1.3%) mozilla-central | 317 650 281 | 317 650 281 | 317 650 281 | 319 613 603 (+0.6%) mozilla-try | 737 555 275 | 737 555 275 | 737 555 275 | 738 079 473 (+0.1%) private-repo | 1 352 362 982 | 1 352 362 982 | 1 346 961 880 | 1 361 327 384 (+0.7%) Speed ------ Timing gathered using `hg perfrevlogwrite -m`. Value are in seconds. mercurial zlib | no | 10x | 5x | 3x | total | 65.551783 | 65.388887 | 65.260658 | 65.321199 | max | 0.034544 | 0.034571 | 0.034659 | 0.034521 | 99.99% | 0.034544 | 0.034571 | 0.034659 | 0.034521 | zstd | no | 10x | 5x | 3x | total | 49.118449 | 49.054062 | 48.753588 | 48.740230 | max | 0.009338 | 0.009239 | 0.009202 | 0.009178 | 99.99% | 0.007618 | 0.007639 | 0.007626 | 0.007621 | pypy zlib | no | 10x | 5x | 3x | total | 560.865984 | 558.983817 | 559.083815 | 559.349152 | max | 0.219614 | 0.215922 | 0.218112 | 0.218107 | 99.99% | 0.219614 | 0.215922 | 0.218112 | 0.218107 | zstd | no | 10x | 5x | 3x | total | 349.393280 | 347.395819 | 347.185407 | 345.643985 | max | 0.084143 | 0.083536 | 0.081834 | 0.082178 | 99.99% | 0.039445 | 0.039639 | 0.039612 | 0.039175 | netbeans zlib | no | 10x | 5x | 3x | total | 33103.327727 | 33314.932260 | 33211.745233 | 33345.891778 | max | 2.666852 | 2.672059 | 2.662453 | 2.662936 | 99.99% | 2.058772 | 2.070429 | 2.069569 | 2.064653 | zstd | no | 10x | 5x | 3x | total | 20112.102708 | 20095.879719 | 20083.390300 | 20123.221859 | max | 2.063482 | 2.062851 | 2.065229 | 2.060147 | 99.99% | 1.146647 | 1.143794 | 1.142933 | 1.146529 | mozilla zlib | no | 10x | 5x | 3x | total | 41374.102138 | 41418.816773 | 41381.956370 | 41334.280732 | max | 3.383474 | 3.387400 | 3.405711 | 3.387316 | 99.99% | 1.006755 | 1.005954 | 1.007700 | 1.007373 | zstd | no | 10x | 5x | 3x | total | 24689.691520 | 24643.939662 | 24664.630027 | 24664.512714 | max | 1.460822 | 1.449640 | 1.439747 | 1.465304 | 99.99% | 0.527111 | 0.527377 | 0.527807 | 0.527226 |

File last commit:

r41202:fbd16845 stable
r42669:4a3abb33 default
Show More
destutil.py
448 lines | 16.5 KiB | text/x-python | PythonLexer
# destutil.py - Mercurial utility function for command destination
#
# Copyright Matt Mackall <mpm@selenic.com> and other
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
from .i18n import _
from . import (
bookmarks,
error,
obsutil,
scmutil,
stack
)
def orphanpossibledestination(repo, rev):
"""Return all changesets that may be a new parent for orphan `rev`.
This function works fine on non-orphan revisions, it's just silly
because there's no destination implied by obsolete markers, so
it'll return nothing.
"""
tonode = repo.changelog.node
parents = repo.changelog.parentrevs
torev = repo.changelog.rev
dest = set()
tovisit = list(parents(rev))
while tovisit:
r = tovisit.pop()
succsets = obsutil.successorssets(repo, tonode(r))
if not succsets:
# if there are no successors for r, r was probably pruned
# and we should walk up to r's parents to try and find
# some successors.
tovisit.extend(parents(r))
else:
# We should probably pick only one destination from split
# (case where '1 < len(ss)'), This could be the currently
# tipmost, but the correct result is less clear when
# results of the split have been moved such that they
# reside on multiple branches.
for ss in succsets:
for n in ss:
dr = torev(n)
if dr != -1:
dest.add(dr)
return dest
def _destupdateobs(repo, clean):
"""decide of an update destination from obsolescence markers"""
node = None
wc = repo[None]
p1 = wc.p1()
movemark = None
if p1.obsolete() and not p1.children():
# allow updating to successors
successors = obsutil.successorssets(repo, p1.node())
# behavior of certain cases is as follows,
#
# divergent changesets: update to highest rev, similar to what
# is currently done when there are more than one head
# (i.e. 'tip')
#
# replaced changesets: same as divergent except we know there
# is no conflict
#
# pruned changeset: no update is done; though, we could
# consider updating to the first non-obsolete parent,
# similar to what is current done for 'hg prune'
if successors:
# flatten the list here handles both divergent (len > 1)
# and the usual case (len = 1)
successors = [n for sub in successors for n in sub]
# get the max revision for the given successors set,
# i.e. the 'tip' of a set
node = repo.revs('max(%ln)', successors).first()
if bookmarks.isactivewdirparent(repo):
movemark = repo['.'].node()
return node, movemark, None
def _destupdatebook(repo, clean):
"""decide on an update destination from active bookmark"""
# we also move the active bookmark, if any
node = None
activemark, movemark = bookmarks.calculateupdate(repo.ui, repo)
if activemark is not None:
node = repo._bookmarks[activemark]
return node, movemark, activemark
def _destupdatebranch(repo, clean):
"""decide on an update destination from current branch
This ignores closed branch heads.
"""
wc = repo[None]
movemark = node = None
currentbranch = wc.branch()
if clean:
currentbranch = repo['.'].branch()
if currentbranch in repo.branchmap():
heads = repo.branchheads(currentbranch)
if heads:
node = repo.revs('max(.::(%ln))', heads).first()
if bookmarks.isactivewdirparent(repo):
movemark = repo['.'].node()
elif currentbranch == 'default' and not wc.p1():
# "null" parent belongs to "default" branch, but it doesn't exist, so
# update to the tipmost non-closed branch head
node = repo.revs('max(head() and not closed())').first()
else:
node = repo['.'].node()
return node, movemark, None
def _destupdatebranchfallback(repo, clean):
"""decide on an update destination from closed heads in current branch"""
wc = repo[None]
currentbranch = wc.branch()
movemark = None
if currentbranch in repo.branchmap():
# here, all descendant branch heads are closed
heads = repo.branchheads(currentbranch, closed=True)
assert heads, "any branch has at least one head"
node = repo.revs('max(.::(%ln))', heads).first()
assert node is not None, ("any revision has at least "
"one descendant branch head")
if bookmarks.isactivewdirparent(repo):
movemark = repo['.'].node()
else:
# here, no "default" branch, and all branches are closed
node = repo.lookup('tip')
assert node is not None, "'tip' exists even in empty repository"
return node, movemark, None
# order in which each step should be evaluated
# steps are run until one finds a destination
destupdatesteps = ['evolution', 'bookmark', 'branch', 'branchfallback']
# mapping to ease extension overriding steps.
destupdatestepmap = {'evolution': _destupdateobs,
'bookmark': _destupdatebook,
'branch': _destupdatebranch,
'branchfallback': _destupdatebranchfallback,
}
def destupdate(repo, clean=False):
"""destination for bare update operation
return (rev, movemark, activemark)
- rev: the revision to update to,
- movemark: node to move the active bookmark from
(cf bookmark.calculate update),
- activemark: a bookmark to activate at the end of the update.
"""
node = movemark = activemark = None
for step in destupdatesteps:
node, movemark, activemark = destupdatestepmap[step](repo, clean)
if node is not None:
break
rev = repo[node].rev()
return rev, movemark, activemark
msgdestmerge = {
# too many matching divergent bookmark
'toomanybookmarks':
{'merge':
(_("multiple matching bookmarks to merge -"
" please merge with an explicit rev or bookmark"),
_("run 'hg heads' to see all heads")),
'rebase':
(_("multiple matching bookmarks to rebase -"
" please rebase to an explicit rev or bookmark"),
_("run 'hg heads' to see all heads")),
},
# no other matching divergent bookmark
'nootherbookmarks':
{'merge':
(_("no matching bookmark to merge - "
"please merge with an explicit rev or bookmark"),
_("run 'hg heads' to see all heads")),
'rebase':
(_("no matching bookmark to rebase - "
"please rebase to an explicit rev or bookmark"),
_("run 'hg heads' to see all heads")),
},
# branch have too many unbookmarked heads, no obvious destination
'toomanyheads':
{'merge':
(_("branch '%s' has %d heads - please merge with an explicit rev"),
_("run 'hg heads .' to see heads")),
'rebase':
(_("branch '%s' has %d heads - please rebase to an explicit rev"),
_("run 'hg heads .' to see heads")),
},
# branch have no other unbookmarked heads
'bookmarkedheads':
{'merge':
(_("heads are bookmarked - please merge with an explicit rev"),
_("run 'hg heads' to see all heads")),
'rebase':
(_("heads are bookmarked - please rebase to an explicit rev"),
_("run 'hg heads' to see all heads")),
},
# branch have just a single heads, but there is other branches
'nootherbranchheads':
{'merge':
(_("branch '%s' has one head - please merge with an explicit rev"),
_("run 'hg heads' to see all heads")),
'rebase':
(_("branch '%s' has one head - please rebase to an explicit rev"),
_("run 'hg heads' to see all heads")),
},
# repository have a single head
'nootherheads':
{'merge':
(_('nothing to merge'),
None),
'rebase':
(_('nothing to rebase'),
None),
},
# repository have a single head and we are not on it
'nootherheadsbehind':
{'merge':
(_('nothing to merge'),
_("use 'hg update' instead")),
'rebase':
(_('nothing to rebase'),
_("use 'hg update' instead")),
},
# We are not on a head
'notatheads':
{'merge':
(_('working directory not at a head revision'),
_("use 'hg update' or merge with an explicit revision")),
'rebase':
(_('working directory not at a head revision'),
_("use 'hg update' or rebase to an explicit revision"))
},
'emptysourceset':
{'merge':
(_('source set is empty'),
None),
'rebase':
(_('source set is empty'),
None),
},
'multiplebranchessourceset':
{'merge':
(_('source set is rooted in multiple branches'),
None),
'rebase':
(_('rebaseset is rooted in multiple named branches'),
_('specify an explicit destination with --dest')),
},
}
def _destmergebook(repo, action='merge', sourceset=None, destspace=None):
"""find merge destination in the active bookmark case"""
node = None
bmheads = bookmarks.headsforactive(repo)
curhead = repo._bookmarks[repo._activebookmark]
if len(bmheads) == 2:
if curhead == bmheads[0]:
node = bmheads[1]
else:
node = bmheads[0]
elif len(bmheads) > 2:
msg, hint = msgdestmerge['toomanybookmarks'][action]
raise error.ManyMergeDestAbort(msg, hint=hint)
elif len(bmheads) <= 1:
msg, hint = msgdestmerge['nootherbookmarks'][action]
raise error.NoMergeDestAbort(msg, hint=hint)
assert node is not None
return node
def _destmergebranch(repo, action='merge', sourceset=None, onheadcheck=True,
destspace=None):
"""find merge destination based on branch heads"""
node = None
if sourceset is None:
sourceset = [repo[repo.dirstate.p1()].rev()]
branch = repo.dirstate.branch()
elif not sourceset:
msg, hint = msgdestmerge['emptysourceset'][action]
raise error.NoMergeDestAbort(msg, hint=hint)
else:
branch = None
for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset):
if branch is not None and ctx.branch() != branch:
msg, hint = msgdestmerge['multiplebranchessourceset'][action]
raise error.ManyMergeDestAbort(msg, hint=hint)
branch = ctx.branch()
bheads = repo.branchheads(branch)
onhead = repo.revs('%ld and %ln', sourceset, bheads)
if onheadcheck and not onhead:
# Case A: working copy if not on a head. (merge only)
#
# This is probably a user mistake We bailout pointing at 'hg update'
if len(repo.heads()) <= 1:
msg, hint = msgdestmerge['nootherheadsbehind'][action]
else:
msg, hint = msgdestmerge['notatheads'][action]
raise error.Abort(msg, hint=hint)
# remove heads descendants of source from the set
bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset))
# filters out bookmarked heads
nbhs = list(repo.revs('%ld - bookmark()', bheads))
if destspace is not None:
# restrict search space
# used in the 'hg pull --rebase' case, see issue 5214.
nbhs = list(repo.revs('%ld and %ld', destspace, nbhs))
if len(nbhs) > 1:
# Case B: There is more than 1 other anonymous heads
#
# This means that there will be more than 1 candidate. This is
# ambiguous. We abort asking the user to pick as explicit destination
# instead.
msg, hint = msgdestmerge['toomanyheads'][action]
msg %= (branch, len(bheads) + 1)
raise error.ManyMergeDestAbort(msg, hint=hint)
elif not nbhs:
# Case B: There is no other anonymous heads
#
# This means that there is no natural candidate to merge with.
# We abort, with various messages for various cases.
if bheads:
msg, hint = msgdestmerge['bookmarkedheads'][action]
elif len(repo.heads()) > 1:
msg, hint = msgdestmerge['nootherbranchheads'][action]
msg %= branch
elif not onhead:
# if 'onheadcheck == False' (rebase case),
# this was not caught in Case A.
msg, hint = msgdestmerge['nootherheadsbehind'][action]
else:
msg, hint = msgdestmerge['nootherheads'][action]
raise error.NoMergeDestAbort(msg, hint=hint)
else:
node = nbhs[0]
assert node is not None
return node
def destmerge(repo, action='merge', sourceset=None, onheadcheck=True,
destspace=None):
"""return the default destination for a merge
(or raise exception about why it can't pick one)
:action: the action being performed, controls emitted error message
"""
# destspace is here to work around issues with `hg pull --rebase` see
# issue5214 for details
if repo._activebookmark:
node = _destmergebook(repo, action=action, sourceset=sourceset,
destspace=destspace)
else:
node = _destmergebranch(repo, action=action, sourceset=sourceset,
onheadcheck=onheadcheck, destspace=destspace)
return repo[node].rev()
def desthistedit(ui, repo):
"""Default base revision to edit for `hg histedit`."""
default = ui.config('histedit', 'defaultrev')
if default is None:
revs = stack.getstack(repo)
elif default:
revs = scmutil.revrange(repo, [default])
else:
raise error.Abort(_("config option histedit.defaultrev can't be empty"))
if revs:
# Take the first revision of the revset as the root
return revs.min()
return None
def stackbase(ui, repo):
revs = stack.getstack(repo)
return revs.first() if revs else None
def _statusotherbook(ui, repo):
bmheads = bookmarks.headsforactive(repo)
curhead = repo._bookmarks[repo._activebookmark]
if repo.revs('%n and parents()', curhead):
# we are on the active bookmark
bmheads = [b for b in bmheads if curhead != b]
if bmheads:
msg = _('%i other divergent bookmarks for "%s"\n')
ui.status(msg % (len(bmheads), repo._activebookmark))
def _statusotherbranchheads(ui, repo):
currentbranch = repo.dirstate.branch()
allheads = repo.branchheads(currentbranch, closed=True)
heads = repo.branchheads(currentbranch)
if repo.revs('%ln and parents()', allheads):
# we are on a head, even though it might be closed
#
# on closed otherheads
# ========= ==========
# o 0 all heads for current branch are closed
# N only descendant branch heads are closed
# x 0 there is only one non-closed branch head
# N there are some non-closed branch heads
# ========= ==========
otherheads = repo.revs('%ln - parents()', heads)
if repo['.'].closesbranch():
ui.warn(_('no open descendant heads on branch "%s", '
'updating to a closed head\n') %
(currentbranch))
if otherheads:
ui.warn(_("(committing will reopen the head, "
"use 'hg heads .' to see %i other heads)\n") %
(len(otherheads)))
else:
ui.warn(_('(committing will reopen branch "%s")\n') %
(currentbranch))
elif otherheads:
curhead = repo['.']
ui.status(_('updated to "%s: %s"\n') % (curhead,
curhead.description().split('\n')[0]))
ui.status(_('%i other heads for branch "%s"\n') %
(len(otherheads), currentbranch))
def statusotherdests(ui, repo):
"""Print message about other head"""
# XXX we should probably include a hint:
# - about what to do
# - how to see such heads
if repo._activebookmark:
_statusotherbook(ui, repo)
else:
_statusotherbranchheads(ui, repo)