diff -r def497c75351 -r bc88aa7472de hgext/chainsaw.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/chainsaw.py Sat Nov 26 12:23:56 2022 +0100 @@ -0,0 +1,156 @@ +# chainsaw.py +# +# Copyright 2022 Georges Racinet +# +# 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 extension provides 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`). +""" + +import shutil + +from mercurial.i18n import _ +from mercurial import ( + cmdutil, + commands, + error, + registrar, +) + +cmdtable = {} +command = registrar.command(cmdtable) +# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. +testedwith = b'ships-with-hg-core' + + +@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'hg admin::chainsaw-update [OPTION] --rev REV --source SOURCE...'), + helpbasic=True, +) +def update(ui, repo, **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: 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: + + - 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'] + 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')) + ui.status(_(b'breaking locks, if any\n')) + repo.svfs.tryunlink(b'lock') + repo.vfs.tryunlink(b'wlock') + + ui.status(_(b'recovering after interrupted transaction, if any\n')) + repo.recover() + + ui.status(_(b'pulling from %s\n') % source) + overrides = {(b'ui', b'quiet'): True} + with ui.configoverride(overrides, b'chainsaw-update'): + pull = cmdutil.findcmd(b'pull', commands.table)[1][0] + pull(ui, repo, source, rev=[rev], remote_hidden=False) + + purge = cmdutil.findcmd(b'purge', commands.table)[1][0] + purge( + ui, + repo, + dirs=True, + all=opts.get('purge_ignored'), + files=opts.get('purge_unknown'), + confirm=False, + ) + + ui.status(_(b'updating to revision \'%s\'\n') % rev) + update = cmdutil.findcmd(b'update', commands.table)[1][0] + update(ui, repo, rev=rev, clean=True) + + ui.status( + _( + b'chainsaw-update to revision \'%s\' ' + b'for repository at \'%s\' done\n' + ) + % (rev, repo.root) + )