##// END OF EJS Templates
branchmap-v3: introduce a "stop_rev" argument to `headsrevs`...
branchmap-v3: introduce a "stop_rev" argument to `headsrevs` The `headsrevs` method of the revlog already have a `revs` argument to compute the headrevs of a limited set of heads. However, it disable the use of the native compiled code to compute the heads, which slows down the branchmap v3 code a lot. The branchmap v3 usage is actually quite constrained as we will always only ignores a part at the top of the graph. So we could be significantly faster. We start by making small change to the python side to improve the situation and introduce the new API. More collaboration with the native code are coming later. This massively speedup operation and close most of the remaining gaps between branchmap-v3 and branchmap-v2. especially on repository with many revs like mozilla-try. A small overhead remains mostly because the `headrevs` logic currently has some inefficiently. We will look into them from there. ### benchmark.name = hg.command.unbundle # bin-env-vars.hg.py-re2-module = default # benchmark.variants.issue6528 = disabled # benchmark.variants.resource-usage = default # benchmark.variants.reuse-external-delta-parent = yes # benchmark.variants.revs = any-1-extra-rev # benchmark.variants.source = unbundle # benchmark.variants.validate = default # benchmark.variants.verbosity = quiet ## data-env-vars.name = netbeans-2018-08-01-zstd-sparse-revlog # bin-env-vars.hg.flavor = default branch-v2: 0.233711 ~~~~~ branch-v3 before: 0.368769 (+57.79%, +0.14) branch-v3 after: 0.239857 (+2.63%, +0.01) # bin-env-vars.hg.flavor = rust branch-v2: 0.235230 ~~~~~ branch-v3 before: 0.372460 (+58.34%, +0.14) branch-v3 after: 0.240972 (+2.44%, +0.01) ## data-env-vars.name = netbeans-2018-08-01-ds2-pnm # bin-env-vars.hg.flavor = rust branch-v2: 0.255586 ~~~~~ branch-v3 before: 0.318907 (+24.78%, +0.06) branch-v3 after: 0.268560 (+5.08%, +0.01) ## data-env-vars.name = mozilla-central-2024-03-22-zstd-sparse-revlog # bin-env-vars.hg.flavor = default branch-v2: 0.339010 ~~~~~ branch-v3 before: 0.349752 (+3.17%, +0.01) branch-v3 after: 0.349389 (+3.06%, +0.01) # bin-env-vars.hg.flavor = rust branch-v2: 0.346525 ~~~~~ branch-v3 before: 0.354300 (+2.24%, +0.01) branch-v3 after: 0.355661 (+2.64%, +0.01) ## data-env-vars.name = mozilla-central-2024-03-22-ds2-pnm # bin-env-vars.hg.flavor = rust branch-v2: 0.380202 ~~~~~ branch-v3 before: 0.396293 (+4.23%, +0.02) branch-v3 after: 0.408851 (+7.54%, +0.03) ## data-env-vars.name = mozilla-unified-2024-03-22-zstd-sparse-revlog # bin-env-vars.hg.flavor = default branch-v2: 0.412165 ~~~~~ branch-v3 before: 0.424769 (+3.06%, +0.01) branch-v3 after: 0.427782 (+3.79%, +0.02) # bin-env-vars.hg.flavor = rust branch-v2: 0.412397 ~~~~~ branch-v3 before: 0.421796 (+2.28%, +0.01) branch-v3 after: 0.422354 (+2.41%, +0.01) ## data-env-vars.name = mozilla-unified-2024-03-22-ds2-pnm # bin-env-vars.hg.flavor = rust branch-v2: 0.429501 ~~~~~ branch-v3 before: 0.443849 (+3.34%, +0.01) branch-v3 after: 0.443197 (+3.19%, +0.01) ## data-env-vars.name = mozilla-try-2024-03-26-zstd-sparse-revlog # bin-env-vars.hg.flavor = default branch-v2: 3.403171 ~~~~~ branch-v3 before: 6.234055 (+83.18%, +2.83) branch-v3 after: 3.819477 (+12.23%, +0.42) # bin-env-vars.hg.flavor = rust branch-v2: 3.454876 ~~~~~ branch-v3 before: 6.307813 (+82.58%, +2.85) branch-v3 after: 3.590284 (+3.92%, +0.14) ## data-env-vars.name = mozilla-try-2024-03-26-ds2-pnm # bin-env-vars.hg.flavor = rust branch-v2: 3.465435 ~~~~~ branch-v3 before: 5.176076 (+49.36%, +1.71) branch-v3 after: 3.633278 (+4.84%, +0.17)

File last commit:

r52757:1c5810ce default
r52870:42a116f1 default
Show More
chainsaw.py
228 lines | 7.4 KiB | text/x-python | PythonLexer
# chainsaw.py
#
# Copyright 2022 Georges Racinet <georges.racinet@octobus.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""chainsaw is a collection of single-minded and dangerous tools. (EXPERIMENTAL)
"Don't use a chainsaw to cut your food!"
The chainsaw is a collection of commands that are so much geared towards a
specific use case in a specific context or environment that they are totally
inappropriate and **really dangerous** in other contexts.
The help text of each command explicitly summarizes its context of application
and the wanted end result.
It is recommended to run these commands with the ``HGPLAIN`` environment
variable (see :hg:`help scripting`).
"""
from __future__ import annotations
import shutil
from ..i18n import _
from .. import (
cmdutil,
commands,
error,
localrepo,
registrar,
)
from ..utils import (
urlutil,
)
cmdtable = {}
command = registrar.command(cmdtable)
@command(
b'admin::chainsaw-update',
[
(
b'',
b'purge-unknown',
True,
_(
b'Remove unversioned files before update. Disabling this can '
b'in some cases interfere with the update.'
b'See also :hg:`purge`.'
),
),
(
b'',
b'purge-ignored',
True,
_(
b'Remove ignored files before update. Disable this for '
b'instance to reuse previous compiler object files. '
b'See also :hg:`purge`.'
),
),
(
b'',
b'rev',
b'',
_(b'revision to update to'),
),
(
b'',
b'source',
b'',
_(b'repository to clone from'),
),
(
b'',
b'dest',
b'',
_(b'repository to update to REV (possibly cloning)'),
),
(
b'',
b'initial-clone-minimal',
False,
_(
b'Pull only the prescribed revision upon initial cloning. '
b'This has the side effect of ignoring clone-bundles, '
b'which if often slower on the client side and stressful '
b'to the server than applying available clone bundles.'
),
),
],
_(
b'hg admin::chainsaw-update [OPTION] --rev REV --source SOURCE --dest DEST'
),
helpbasic=True,
norepo=True,
)
def update(ui, **opts):
"""pull and update to a given revision, no matter what, (EXPERIMENTAL)
Context of application: *some* Continuous Integration (CI) systems,
packaging or deployment tools.
Wanted end result: local repository at the given REPO_PATH, having the
latest changes to the given revision and with a clean working directory
updated at the given revision.
chainsaw-update pulls from one source, then updates the working directory
to the given revision, overcoming anything that would stand in the way.
By default, it will:
- clone if the local repo does not exist yet, **removing any directory
at the given path** that would not be a Mercurial repository.
The initial clone is full by default, so that clonebundles can be
applied. Use the --initial-clone-minimal flag to avoid this.
- break locks if needed, leading to possible corruption if there
is a concurrent write access.
- perform recovery actions if needed
- revert any local modification.
- purge unknown and ignored files.
- go as far as to reclone if everything else failed (not implemented yet).
DO NOT use it for anything else than performing a series
of unattended updates, with full exclusive repository access each time
and without any other local work than running build scripts.
In case the local repository is a share (see :hg:`help share`), exclusive
write access to the share source is also mandatory.
It is recommended to run these commands with the ``HGPLAIN`` environment
variable (see :hg:`scripting`).
Motivation: in Continuous Integration and Delivery systems (CI/CD), the
occasional remnant or bogus lock are common sources of waste of time (both
working time and calendar time). CI/CD scripts tend to grow with counter-
measures, often done in urgency. Also, whilst it is neat to keep
repositories from one job to the next (especially with large
repositories), an exceptional recloning is better than missing a release
deadline.
"""
rev = opts['rev']
source = opts['source']
repo_path = opts['dest']
if not rev:
raise error.InputError(_(b'specify a target revision with --rev'))
if not source:
raise error.InputError(_(b'specify a pull path with --source'))
if not repo_path:
raise error.InputError(_(b'specify a repo path with --dest'))
repo_path = urlutil.urllocalpath(repo_path)
try:
repo = localrepo.instance(ui, repo_path, create=False)
repo_created = False
ui.status(_(b'loaded repository at "%s"\n' % repo_path))
except error.RepoError:
try:
shutil.rmtree(repo_path)
except FileNotFoundError:
ui.status(_(b'no such directory: "%s"\n' % repo_path))
else:
ui.status(
_(
b'removed non-repository file or directory '
b'at "%s"' % repo_path
)
)
ui.status(_(b'creating repository at "%s"\n' % repo_path))
repo = localrepo.instance(ui, repo_path, create=True)
repo_created = True
if repo.svfs.tryunlink(b'lock'):
ui.status(_(b'had to break store lock\n'))
if repo.vfs.tryunlink(b'wlock'):
ui.status(_(b'had to break working copy lock\n'))
# If another process relock after the breacking above, the next locking
# will have to wait.
with repo.wlock(), repo.lock():
ui.status(_(b'recovering after interrupted transaction, if any\n'))
repo.recover()
ui.status(_(b'pulling from %s\n') % source)
if repo_created and not opts.get('initial_clone_minimal'):
pull_revs = []
else:
pull_revs = [rev]
overrides = {(b'ui', b'quiet'): True}
with repo.ui.configoverride(overrides, b'chainsaw-update'):
pull = cmdutil.findcmd(b'pull', commands.table)[1][0]
ret = pull(
repo.ui,
repo,
source,
rev=pull_revs,
remote_hidden=False,
)
if ret:
return ret
purge = cmdutil.findcmd(b'purge', commands.table)[1][0]
ret = purge(
ui,
repo,
dirs=True,
all=opts.get('purge_ignored'),
files=opts.get('purge_unknown'),
confirm=False,
)
if ret:
return ret
ui.status(_(b'updating to revision \'%s\'\n') % rev)
update = cmdutil.findcmd(b'update', commands.table)[1][0]
ret = update(ui, repo, rev=rev, clean=True)
if ret:
return ret
ui.status(
_(
b'chainsaw-update to revision \'%s\' '
b'for repository at \'%s\' done\n'
)
% (rev, repo.root)
)