##// END OF EJS Templates
chainsaw: new extension for dangerous operations...
Georges Racinet -
r52322:bc88aa74 default
parent child Browse files
Show More
@@ -0,0 +1,156 b''
1 # chainsaw.py
2 #
3 # Copyright 2022 Georges Racinet <georges.racinet@octobus.net>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7 """chainsaw is a collection of single-minded and dangerous tools. (EXPERIMENTAL)
8
9 "Don't use a chainsaw to cut your food!"
10
11 The chainsaw extension provides commands that are so much geared towards a
12 specific use case in a specific context or environment that they are totally
13 inappropriate and **really dangerous** in other contexts.
14
15 The help text of each command explicitly summarizes its context of application
16 and the wanted end result.
17
18 It is recommended to run these commands with the ``HGPLAIN`` environment
19 variable (see :hg:`help scripting`).
20 """
21
22 import shutil
23
24 from mercurial.i18n import _
25 from mercurial import (
26 cmdutil,
27 commands,
28 error,
29 registrar,
30 )
31
32 cmdtable = {}
33 command = registrar.command(cmdtable)
34 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
36 # be specifying the version(s) of Mercurial they are tested with, or
37 # leave the attribute unspecified.
38 testedwith = b'ships-with-hg-core'
39
40
41 @command(
42 b'admin::chainsaw-update',
43 [
44 (
45 b'',
46 b'purge-unknown',
47 True,
48 _(
49 b'Remove unversioned files before update. Disabling this can '
50 b'in some cases interfere with the update.'
51 b'See also :hg:`purge`.'
52 ),
53 ),
54 (
55 b'',
56 b'purge-ignored',
57 True,
58 _(
59 b'Remove ignored files before update. Disable this for '
60 b'instance to reuse previous compiler object files. '
61 b'See also :hg:`purge`.'
62 ),
63 ),
64 (
65 b'',
66 b'rev',
67 b'',
68 _(b'revision to update to'),
69 ),
70 (
71 b'',
72 b'source',
73 b'',
74 _(b'repository to clone from'),
75 ),
76 ],
77 _(b'hg admin::chainsaw-update [OPTION] --rev REV --source SOURCE...'),
78 helpbasic=True,
79 )
80 def update(ui, repo, **opts):
81 """pull and update to a given revision, no matter what, (EXPERIMENTAL)
82
83 Context of application: *some* Continuous Integration (CI) systems,
84 packaging or deployment tools.
85
86 Wanted end result: clean working directory updated at the given revision.
87
88 chainsaw-update pulls from one source, then updates the working directory
89 to the given revision, overcoming anything that would stand in the way.
90
91 By default, it will:
92
93 - break locks if needed, leading to possible corruption if there
94 is a concurrent write access.
95 - perform recovery actions if needed
96 - revert any local modification.
97 - purge unknown and ignored files.
98 - go as far as to reclone if everything else failed (not implemented yet).
99
100 DO NOT use it for anything else than performing a series
101 of unattended updates, with full exclusive repository access each time
102 and without any other local work than running build scripts.
103 In case the local repository is a share (see :hg:`help share`), exclusive
104 write access to the share source is also mandatory.
105
106 It is recommended to run these commands with the ``HGPLAIN`` environment
107 variable (see :hg:`scripting`).
108
109 Motivation: in Continuous Integration and Delivery systems (CI/CD), the
110 occasional remnant or bogus lock are common sources of waste of time (both
111 working time and calendar time). CI/CD scripts tend to grow with counter-
112 measures, often done in urgency. Also, whilst it is neat to keep
113 repositories from one job to the next (especially with large
114 repositories), an exceptional recloning is better than missing a release
115 deadline.
116 """
117 rev = opts['rev']
118 source = opts['source']
119 if not rev:
120 raise error.InputError(_(b'specify a target revision with --rev'))
121 if not source:
122 raise error.InputError(_(b'specify a pull path with --source'))
123 ui.status(_(b'breaking locks, if any\n'))
124 repo.svfs.tryunlink(b'lock')
125 repo.vfs.tryunlink(b'wlock')
126
127 ui.status(_(b'recovering after interrupted transaction, if any\n'))
128 repo.recover()
129
130 ui.status(_(b'pulling from %s\n') % source)
131 overrides = {(b'ui', b'quiet'): True}
132 with ui.configoverride(overrides, b'chainsaw-update'):
133 pull = cmdutil.findcmd(b'pull', commands.table)[1][0]
134 pull(ui, repo, source, rev=[rev], remote_hidden=False)
135
136 purge = cmdutil.findcmd(b'purge', commands.table)[1][0]
137 purge(
138 ui,
139 repo,
140 dirs=True,
141 all=opts.get('purge_ignored'),
142 files=opts.get('purge_unknown'),
143 confirm=False,
144 )
145
146 ui.status(_(b'updating to revision \'%s\'\n') % rev)
147 update = cmdutil.findcmd(b'update', commands.table)[1][0]
148 update(ui, repo, rev=rev, clean=True)
149
150 ui.status(
151 _(
152 b'chainsaw-update to revision \'%s\' '
153 b'for repository at \'%s\' done\n'
154 )
155 % (rev, repo.root)
156 )
@@ -0,0 +1,105 b''
1 ============================================
2 Tests for the admin::chainsaw-update command
3 ============================================
4
5 setup
6 =====
7
8 $ cat >> $HGRCPATH << EOF
9 > [extensions]
10 > chainsaw=
11 > EOF
12
13 $ hg init src
14 $ cd src
15 $ echo 1 > foo
16 $ hg ci -Am1
17 adding foo
18 $ cd ..
19
20 Actual tests
21 ============
22
23 Simple invocation
24 -----------------
25
26 $ hg init repo
27 $ cd repo
28 $ hg admin::chainsaw-update --rev default --source ../src
29 breaking locks, if any
30 recovering after interrupted transaction, if any
31 no interrupted transaction available
32 pulling from ../src
33 updating to revision 'default'
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 chainsaw-update to revision 'default' for repository at '$TESTTMP/repo' done
36
37 $ cat foo
38 1
39
40 Test lock breacking capabilities
41 --------------------------------
42
43 Demonstrate lock-breaking capabilities with locks that regular Mercurial
44 operation would not break, because the hostnames registered in locks differ
45 from the current hostname (happens a lot with succesive containers):
46
47 $ ln -s invalid.host.test/effffffc:171814 .hg/store/lock
48 $ ln -s invalid.host.test/effffffc:171814 .hg/wlock
49 $ hg debuglock
50 lock: (.*?), process 171814, host invalid.host.test/effffffc \((\d+)s\) (re)
51 wlock: (.*?), process 171814, host invalid.host.test/effffffc \((\d+)s\) (re)
52 [2]
53
54 $ hg admin::chainsaw-update --no-purge-ignored --rev default --source ../src -q
55 no interrupted transaction available
56
57 Test file purging capabilities
58 ------------------------------
59
60 Let's also add local modifications (tracked and untracked) to demonstrate the
61 purging.
62
63 $ echo untracked > bar
64 $ echo modified > foo
65 $ hg status -A
66 M foo
67 ? bar
68
69 $ echo 2 > ../src/foo
70 $ hg -R ../src commit -m2
71 $ hg admin::chainsaw-update --rev default --source ../src -q
72 no interrupted transaction available
73 $ hg status -A
74 C foo
75 $ cat foo
76 2
77
78 Now behaviour with respect to ignored files: they are not purged if
79 the --no-purge-ignored flag is passed, but they are purged by default
80
81 $ echo bar > .hgignore
82 $ hg ci -Aqm hgignore
83 $ echo ignored > bar
84 $ hg status --all
85 I bar
86 C .hgignore
87 C foo
88
89 $ hg admin::chainsaw-update --no-purge-ignored --rev default --source ../src -q
90 no interrupted transaction available
91 $ hg status --all
92 I bar
93 C .hgignore
94 C foo
95 $ cat bar
96 ignored
97
98 $ hg admin::chainsaw-update --rev default --source ../src -q
99 no interrupted transaction available
100 $ hg status --all
101 C .hgignore
102 C foo
103 $ test -f bar
104 [1]
105
General Comments 0
You need to be logged in to leave comments. Login now