##// END OF EJS Templates
chainsaw-update: exit early if one of the intermediate command fails...
marmoute -
r52328:ad106653 default
parent child Browse files
Show More
@@ -1,219 +1,231 b''
1 1 # chainsaw.py
2 2 #
3 3 # Copyright 2022 Georges Racinet <georges.racinet@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """chainsaw is a collection of single-minded and dangerous tools. (EXPERIMENTAL)
8 8
9 9 "Don't use a chainsaw to cut your food!"
10 10
11 11 The chainsaw extension provides commands that are so much geared towards a
12 12 specific use case in a specific context or environment that they are totally
13 13 inappropriate and **really dangerous** in other contexts.
14 14
15 15 The help text of each command explicitly summarizes its context of application
16 16 and the wanted end result.
17 17
18 18 It is recommended to run these commands with the ``HGPLAIN`` environment
19 19 variable (see :hg:`help scripting`).
20 20 """
21 21
22 22 import shutil
23 23
24 24 from mercurial.i18n import _
25 25 from mercurial import (
26 26 cmdutil,
27 27 commands,
28 28 error,
29 29 localrepo,
30 30 registrar,
31 31 )
32 32 from mercurial.utils import (
33 33 urlutil,
34 34 )
35 35
36 36 cmdtable = {}
37 37 command = registrar.command(cmdtable)
38 38 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
39 39 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
40 40 # be specifying the version(s) of Mercurial they are tested with, or
41 41 # leave the attribute unspecified.
42 42 testedwith = b'ships-with-hg-core'
43 43
44 44
45 45 @command(
46 46 b'admin::chainsaw-update',
47 47 [
48 48 (
49 49 b'',
50 50 b'purge-unknown',
51 51 True,
52 52 _(
53 53 b'Remove unversioned files before update. Disabling this can '
54 54 b'in some cases interfere with the update.'
55 55 b'See also :hg:`purge`.'
56 56 ),
57 57 ),
58 58 (
59 59 b'',
60 60 b'purge-ignored',
61 61 True,
62 62 _(
63 63 b'Remove ignored files before update. Disable this for '
64 64 b'instance to reuse previous compiler object files. '
65 65 b'See also :hg:`purge`.'
66 66 ),
67 67 ),
68 68 (
69 69 b'',
70 70 b'rev',
71 71 b'',
72 72 _(b'revision to update to'),
73 73 ),
74 74 (
75 75 b'',
76 76 b'source',
77 77 b'',
78 78 _(b'repository to clone from'),
79 79 ),
80 80 (
81 81 b'',
82 82 b'dest',
83 83 b'',
84 84 _(b'repository to update to REV (possibly cloning)'),
85 85 ),
86 86 (
87 87 b'',
88 88 b'initial-clone-minimal',
89 89 False,
90 90 _(
91 91 b'Pull only the prescribed revision upon initial cloning. '
92 92 b'This has the side effect of ignoring clone-bundles, '
93 93 b'which if often slower on the client side and stressful '
94 94 b'to the server than applying available clone bundles.'
95 95 ),
96 96 ),
97 97 ],
98 98 _(
99 99 b'hg admin::chainsaw-update [OPTION] --rev REV --source SOURCE --dest DEST'
100 100 ),
101 101 helpbasic=True,
102 102 norepo=True,
103 103 )
104 104 def update(ui, **opts):
105 105 """pull and update to a given revision, no matter what, (EXPERIMENTAL)
106 106
107 107 Context of application: *some* Continuous Integration (CI) systems,
108 108 packaging or deployment tools.
109 109
110 110 Wanted end result: local repository at the given REPO_PATH, having the
111 111 latest changes to the given revision and with a clean working directory
112 112 updated at the given revision.
113 113
114 114 chainsaw-update pulls from one source, then updates the working directory
115 115 to the given revision, overcoming anything that would stand in the way.
116 116
117 117 By default, it will:
118 118
119 119 - clone if the local repo does not exist yet, **removing any directory
120 120 at the given path** that would not be a Mercurial repository.
121 121 The initial clone is full by default, so that clonebundles can be
122 122 applied. Use the --initial-clone-minimal flag to avoid this.
123 123 - break locks if needed, leading to possible corruption if there
124 124 is a concurrent write access.
125 125 - perform recovery actions if needed
126 126 - revert any local modification.
127 127 - purge unknown and ignored files.
128 128 - go as far as to reclone if everything else failed (not implemented yet).
129 129
130 130 DO NOT use it for anything else than performing a series
131 131 of unattended updates, with full exclusive repository access each time
132 132 and without any other local work than running build scripts.
133 133 In case the local repository is a share (see :hg:`help share`), exclusive
134 134 write access to the share source is also mandatory.
135 135
136 136 It is recommended to run these commands with the ``HGPLAIN`` environment
137 137 variable (see :hg:`scripting`).
138 138
139 139 Motivation: in Continuous Integration and Delivery systems (CI/CD), the
140 140 occasional remnant or bogus lock are common sources of waste of time (both
141 141 working time and calendar time). CI/CD scripts tend to grow with counter-
142 142 measures, often done in urgency. Also, whilst it is neat to keep
143 143 repositories from one job to the next (especially with large
144 144 repositories), an exceptional recloning is better than missing a release
145 145 deadline.
146 146 """
147 147 rev = opts['rev']
148 148 source = opts['source']
149 149 repo_path = opts['dest']
150 150 if not rev:
151 151 raise error.InputError(_(b'specify a target revision with --rev'))
152 152 if not source:
153 153 raise error.InputError(_(b'specify a pull path with --source'))
154 154 if not repo_path:
155 155 raise error.InputError(_(b'specify a repo path with --dest'))
156 156 repo_path = urlutil.urllocalpath(repo_path)
157 157
158 158 try:
159 159 repo = localrepo.instance(ui, repo_path, create=False)
160 160 repo_created = False
161 161 ui.status(_(b'loaded repository at "%s"\n' % repo_path))
162 162 except error.RepoError:
163 163 try:
164 164 shutil.rmtree(repo_path)
165 165 except FileNotFoundError:
166 166 ui.status(_(b'no such directory: "%s"\n' % repo_path))
167 167 else:
168 168 ui.status(
169 169 _(
170 170 b'removed non-repository file or directory '
171 171 b'at "%s"' % repo_path
172 172 )
173 173 )
174 174
175 175 ui.status(_(b'creating repository at "%s"\n' % repo_path))
176 176 repo = localrepo.instance(ui, repo_path, create=True)
177 177 repo_created = True
178 178
179 179 if repo.svfs.tryunlink(b'lock'):
180 180 ui.status(_(b'had to break store lock\n'))
181 181 if repo.vfs.tryunlink(b'wlock'):
182 182 ui.status(_(b'had to break working copy lock\n'))
183 183 # If another process relock after the breacking above, the next locking
184 184 # will have to wait.
185 185 with repo.wlock(), repo.lock():
186 186 ui.status(_(b'recovering after interrupted transaction, if any\n'))
187 187 repo.recover()
188 188
189 189 ui.status(_(b'pulling from %s\n') % source)
190 190 if repo_created and not opts.get('initial_clone_minimal'):
191 191 pull_revs = []
192 192 else:
193 193 pull_revs = [rev]
194 194 overrides = {(b'ui', b'quiet'): True}
195 195 with repo.ui.configoverride(overrides, b'chainsaw-update'):
196 196 pull = cmdutil.findcmd(b'pull', commands.table)[1][0]
197 pull(repo.ui, repo, source, rev=pull_revs, remote_hidden=False)
197 ret = pull(
198 repo.ui,
199 repo,
200 source,
201 rev=pull_revs,
202 remote_hidden=False,
203 )
204 if ret:
205 return ret
198 206
199 207 purge = cmdutil.findcmd(b'purge', commands.table)[1][0]
200 purge(
208 ret = purge(
201 209 ui,
202 210 repo,
203 211 dirs=True,
204 212 all=opts.get('purge_ignored'),
205 213 files=opts.get('purge_unknown'),
206 214 confirm=False,
207 215 )
216 if ret:
217 return ret
208 218
209 219 ui.status(_(b'updating to revision \'%s\'\n') % rev)
210 220 update = cmdutil.findcmd(b'update', commands.table)[1][0]
211 update(ui, repo, rev=rev, clean=True)
221 ret = update(ui, repo, rev=rev, clean=True)
222 if ret:
223 return ret
212 224
213 225 ui.status(
214 226 _(
215 227 b'chainsaw-update to revision \'%s\' '
216 228 b'for repository at \'%s\' done\n'
217 229 )
218 230 % (rev, repo.root)
219 231 )
General Comments 0
You need to be logged in to leave comments. Login now